@goodandready/dsh-goal 0.2.16 → 0.2.18
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 +19 -26
- package/lib/client.js +53 -102
- package/lib/index.js +97 -63
- package/package.json +4 -4
package/lib/card-form-state.js
CHANGED
|
@@ -10,9 +10,19 @@ export const DEFAULT_SETTINGS = {
|
|
|
10
10
|
showQuickLaunchButton: true,
|
|
11
11
|
consecutiveToolFailureLimit: 3,
|
|
12
12
|
enableBrowserNotifications: true,
|
|
13
|
+
maxTokenBudget: 0,
|
|
14
|
+
budgetWarningThreshold: 80,
|
|
15
|
+
autoCheckpointOnMilestone: false,
|
|
13
16
|
storagePath: '',
|
|
14
17
|
};
|
|
15
18
|
|
|
19
|
+
export const NUMERIC_FIELDS = {
|
|
20
|
+
maxIterations: 1,
|
|
21
|
+
consecutiveToolFailureLimit: 0,
|
|
22
|
+
maxTokenBudget: 0,
|
|
23
|
+
budgetWarningThreshold: 0,
|
|
24
|
+
};
|
|
25
|
+
|
|
16
26
|
/**
|
|
17
27
|
* Parse numeric settings field:
|
|
18
28
|
* - empty string / null / undefined -> undefined (reset override)
|
|
@@ -50,28 +60,14 @@ export function getFieldStatus(field, draftValue, snap) {
|
|
|
50
60
|
let invalid = false;
|
|
51
61
|
let isDirty = false;
|
|
52
62
|
|
|
53
|
-
if (field
|
|
63
|
+
if (field in NUMERIC_FIELDS) {
|
|
54
64
|
if (draftValue !== undefined) {
|
|
55
65
|
if (draftValue === '') {
|
|
56
66
|
invalid = false;
|
|
57
67
|
isDirty = userVal !== undefined;
|
|
58
68
|
} else {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
invalid = true;
|
|
62
|
-
isDirty = true;
|
|
63
|
-
} else {
|
|
64
|
-
isDirty = parsed !== (userVal !== undefined ? userVal : baseVal);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
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);
|
|
69
|
+
const min = NUMERIC_FIELDS[field];
|
|
70
|
+
const parsed = parseNumberField(draftValue, min);
|
|
75
71
|
if (Number.isNaN(parsed)) {
|
|
76
72
|
invalid = true;
|
|
77
73
|
isDirty = true;
|
|
@@ -86,7 +82,7 @@ export function getFieldStatus(field, draftValue, snap) {
|
|
|
86
82
|
isDirty = cleanVal !== (userVal !== undefined ? userVal : (baseVal || ''));
|
|
87
83
|
}
|
|
88
84
|
} else {
|
|
89
|
-
// boolean fields (autoDrive, enableSound, showQuickLaunchButton, enableBrowserNotifications)
|
|
85
|
+
// boolean fields (autoDrive, enableSound, showQuickLaunchButton, enableBrowserNotifications, autoCheckpointOnMilestone)
|
|
90
86
|
if (draftValue !== undefined) {
|
|
91
87
|
isDirty = Boolean(draftValue) !== (userVal !== undefined ? Boolean(userVal) : Boolean(baseVal));
|
|
92
88
|
}
|
|
@@ -119,6 +115,9 @@ export function computeSavePlan(draft, snap) {
|
|
|
119
115
|
'showQuickLaunchButton',
|
|
120
116
|
'consecutiveToolFailureLimit',
|
|
121
117
|
'enableBrowserNotifications',
|
|
118
|
+
'maxTokenBudget',
|
|
119
|
+
'budgetWarningThreshold',
|
|
120
|
+
'autoCheckpointOnMilestone',
|
|
122
121
|
'storagePath',
|
|
123
122
|
];
|
|
124
123
|
|
|
@@ -129,17 +128,11 @@ export function computeSavePlan(draft, snap) {
|
|
|
129
128
|
if (status.invalid) continue;
|
|
130
129
|
|
|
131
130
|
if (status.isDirty) {
|
|
132
|
-
if (f
|
|
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') {
|
|
131
|
+
if (f in NUMERIC_FIELDS) {
|
|
139
132
|
if (draft[f] === '') {
|
|
140
133
|
writes.push([f, undefined]);
|
|
141
134
|
} else {
|
|
142
|
-
writes.push([f, parseNumberField(draft[f],
|
|
135
|
+
writes.push([f, parseNumberField(draft[f], NUMERIC_FIELDS[f])]);
|
|
143
136
|
}
|
|
144
137
|
} else if (f === 'storagePath') {
|
|
145
138
|
const clean = typeof draft[f] === 'string' ? draft[f].trim() : '';
|
package/lib/client.js
CHANGED
|
@@ -3794,153 +3794,125 @@ window.__ModuleLoader__.load({
|
|
|
3794
3794
|
// --- SETTINGS FORM LOGIC ---
|
|
3795
3795
|
|
|
3796
3796
|
const DEFAULT_SETTINGS = {
|
|
3797
|
-
|
|
3798
3797
|
maxIterations: 25,
|
|
3799
|
-
|
|
3800
3798
|
autoDrive: true,
|
|
3801
|
-
|
|
3802
3799
|
enableSound: true,
|
|
3803
|
-
|
|
3804
3800
|
showQuickLaunchButton: true,
|
|
3805
|
-
|
|
3806
3801
|
consecutiveToolFailureLimit: 3,
|
|
3807
|
-
|
|
3808
3802
|
enableBrowserNotifications: true,
|
|
3809
|
-
|
|
3803
|
+
maxTokenBudget: 0,
|
|
3804
|
+
budgetWarningThreshold: 80,
|
|
3805
|
+
autoCheckpointOnMilestone: false,
|
|
3806
|
+
storagePath: '',
|
|
3810
3807
|
};
|
|
3811
3808
|
|
|
3812
|
-
|
|
3809
|
+
const NUMERIC_FIELDS = {
|
|
3810
|
+
maxIterations: 1,
|
|
3811
|
+
consecutiveToolFailureLimit: 0,
|
|
3812
|
+
maxTokenBudget: 0,
|
|
3813
|
+
budgetWarningThreshold: 0,
|
|
3814
|
+
};
|
|
3813
3815
|
|
|
3816
|
+
function parseNumberField(raw, min = 1) {
|
|
3814
3817
|
if (raw === '' || raw === null || raw === undefined) {
|
|
3815
|
-
|
|
3816
3818
|
return undefined;
|
|
3817
|
-
|
|
3818
3819
|
}
|
|
3819
|
-
|
|
3820
3820
|
const n = Number(raw);
|
|
3821
|
-
|
|
3822
|
-
if (!Number.isFinite(n) || n < 1 || !Number.isInteger(n)) {
|
|
3823
|
-
|
|
3821
|
+
if (!Number.isFinite(n) || n < min || !Number.isInteger(n)) {
|
|
3824
3822
|
return NaN;
|
|
3825
|
-
|
|
3826
3823
|
}
|
|
3827
|
-
|
|
3828
3824
|
return n;
|
|
3829
|
-
|
|
3830
3825
|
}
|
|
3831
3826
|
|
|
3832
3827
|
function getFieldStatus(field, draftValue, snap) {
|
|
3833
|
-
|
|
3834
3828
|
const baseVal = snap?.base?.[field] ?? DEFAULT_SETTINGS[field];
|
|
3835
|
-
|
|
3836
3829
|
const userVal = snap?.user?.[field];
|
|
3837
|
-
|
|
3838
3830
|
const effectiveVal = snap?.value?.[field] ?? baseVal;
|
|
3839
3831
|
|
|
3840
3832
|
const isOverridden = userVal !== undefined && userVal !== null;
|
|
3841
|
-
|
|
3842
3833
|
const currentVal = draftValue !== undefined ? draftValue : effectiveVal;
|
|
3843
3834
|
|
|
3844
3835
|
let invalid = false;
|
|
3845
|
-
|
|
3846
3836
|
let isDirty = false;
|
|
3847
3837
|
|
|
3848
|
-
if (field
|
|
3849
|
-
|
|
3838
|
+
if (field in NUMERIC_FIELDS) {
|
|
3850
3839
|
if (draftValue !== undefined) {
|
|
3851
|
-
|
|
3852
3840
|
if (draftValue === '') {
|
|
3853
|
-
|
|
3854
3841
|
invalid = false;
|
|
3855
|
-
|
|
3856
3842
|
isDirty = userVal !== undefined;
|
|
3857
|
-
|
|
3858
3843
|
} else {
|
|
3859
|
-
|
|
3860
|
-
const parsed = parseNumberField(draftValue);
|
|
3861
|
-
|
|
3844
|
+
const min = NUMERIC_FIELDS[field];
|
|
3845
|
+
const parsed = parseNumberField(draftValue, min);
|
|
3862
3846
|
if (Number.isNaN(parsed)) {
|
|
3863
|
-
|
|
3864
3847
|
invalid = true;
|
|
3865
|
-
|
|
3866
3848
|
isDirty = true;
|
|
3867
|
-
|
|
3868
3849
|
} else {
|
|
3869
|
-
|
|
3870
3850
|
isDirty = parsed !== (userVal !== undefined ? userVal : baseVal);
|
|
3871
|
-
|
|
3872
3851
|
}
|
|
3873
|
-
|
|
3874
3852
|
}
|
|
3875
|
-
|
|
3876
3853
|
}
|
|
3877
|
-
|
|
3854
|
+
} else if (field === 'storagePath') {
|
|
3855
|
+
if (draftValue !== undefined) {
|
|
3856
|
+
const cleanVal = typeof draftValue === 'string' ? draftValue.trim() : '';
|
|
3857
|
+
isDirty = cleanVal !== (userVal !== undefined ? userVal : (baseVal || ''));
|
|
3858
|
+
}
|
|
3878
3859
|
} else {
|
|
3879
|
-
|
|
3880
3860
|
if (draftValue !== undefined) {
|
|
3881
|
-
|
|
3882
3861
|
isDirty = Boolean(draftValue) !== (userVal !== undefined ? Boolean(userVal) : Boolean(baseVal));
|
|
3883
|
-
|
|
3884
3862
|
}
|
|
3885
|
-
|
|
3886
3863
|
}
|
|
3887
3864
|
|
|
3888
3865
|
return {
|
|
3889
|
-
|
|
3890
3866
|
value: currentVal,
|
|
3891
|
-
|
|
3892
3867
|
baseValue: baseVal,
|
|
3893
|
-
|
|
3894
3868
|
userValue: userVal,
|
|
3895
|
-
|
|
3896
3869
|
isOverridden,
|
|
3897
|
-
|
|
3898
3870
|
isDirty,
|
|
3899
|
-
|
|
3900
3871
|
invalid,
|
|
3901
|
-
|
|
3902
3872
|
};
|
|
3903
|
-
|
|
3904
3873
|
}
|
|
3905
3874
|
|
|
3906
3875
|
function computeSavePlan(draft, snap) {
|
|
3907
|
-
|
|
3908
3876
|
if (!draft) return [];
|
|
3909
3877
|
|
|
3910
|
-
const fields = [
|
|
3878
|
+
const fields = [
|
|
3879
|
+
'maxIterations',
|
|
3880
|
+
'autoDrive',
|
|
3881
|
+
'enableSound',
|
|
3882
|
+
'showQuickLaunchButton',
|
|
3883
|
+
'consecutiveToolFailureLimit',
|
|
3884
|
+
'enableBrowserNotifications',
|
|
3885
|
+
'maxTokenBudget',
|
|
3886
|
+
'budgetWarningThreshold',
|
|
3887
|
+
'autoCheckpointOnMilestone',
|
|
3888
|
+
'storagePath',
|
|
3889
|
+
];
|
|
3911
3890
|
|
|
3912
3891
|
const writes = [];
|
|
3913
3892
|
|
|
3914
3893
|
for (const f of fields) {
|
|
3915
|
-
|
|
3916
3894
|
if (draft[f] === undefined) continue;
|
|
3917
3895
|
|
|
3918
3896
|
const status = getFieldStatus(f, draft[f], snap);
|
|
3919
|
-
|
|
3920
3897
|
if (status.invalid) continue;
|
|
3921
3898
|
|
|
3922
3899
|
if (status.isDirty) {
|
|
3923
|
-
|
|
3924
|
-
|
|
3925
|
-
|
|
3926
|
-
|
|
3927
|
-
|
|
3928
|
-
|
|
3929
|
-
|
|
3930
|
-
|
|
3931
|
-
|
|
3900
|
+
if (f in NUMERIC_FIELDS) {
|
|
3901
|
+
if (draft[f] === '') {
|
|
3902
|
+
writes.push([f, undefined]);
|
|
3903
|
+
} else {
|
|
3904
|
+
writes.push([f, parseNumberField(draft[f], NUMERIC_FIELDS[f])]);
|
|
3905
|
+
}
|
|
3906
|
+
} else if (f === 'storagePath') {
|
|
3907
|
+
const clean = typeof draft[f] === 'string' ? draft[f].trim() : '';
|
|
3908
|
+
writes.push([f, clean ? clean : undefined]);
|
|
3932
3909
|
} else {
|
|
3933
|
-
|
|
3934
3910
|
writes.push([f, Boolean(draft[f])]);
|
|
3935
|
-
|
|
3936
3911
|
}
|
|
3937
|
-
|
|
3938
3912
|
}
|
|
3939
|
-
|
|
3940
3913
|
}
|
|
3941
3914
|
|
|
3942
3915
|
return writes;
|
|
3943
|
-
|
|
3944
3916
|
}
|
|
3945
3917
|
|
|
3946
3918
|
// --- SETTINGS CARD ---
|
|
@@ -4138,17 +4110,11 @@ window.__ModuleLoader__.load({
|
|
|
4138
4110
|
};
|
|
4139
4111
|
|
|
4140
4112
|
const resetFieldToDefault = (field) => {
|
|
4141
|
-
|
|
4142
|
-
if (field === 'maxIterations') {
|
|
4143
|
-
|
|
4113
|
+
if (field in NUMERIC_FIELDS) {
|
|
4144
4114
|
edit(field, '');
|
|
4145
|
-
|
|
4146
4115
|
} else {
|
|
4147
|
-
|
|
4148
4116
|
edit(field, snap?.base?.[field] ?? DEFAULT_SETTINGS[field]);
|
|
4149
|
-
|
|
4150
4117
|
}
|
|
4151
|
-
|
|
4152
4118
|
};
|
|
4153
4119
|
|
|
4154
4120
|
const save = async () => {
|
|
@@ -4612,7 +4578,7 @@ window.__ModuleLoader__.load({
|
|
|
4612
4578
|
|
|
4613
4579
|
title: t('resetField') || 'Reset to default',
|
|
4614
4580
|
|
|
4615
|
-
onClick: () =>
|
|
4581
|
+
onClick: () => resetFieldToDefault('consecutiveToolFailureLimit'),
|
|
4616
4582
|
|
|
4617
4583
|
},
|
|
4618
4584
|
|
|
@@ -4644,7 +4610,7 @@ window.__ModuleLoader__.load({
|
|
|
4644
4610
|
|
|
4645
4611
|
className: 'dsh-goal-card-input',
|
|
4646
4612
|
|
|
4647
|
-
onChange: (e) => edit('consecutiveToolFailureLimit',
|
|
4613
|
+
onChange: (e) => edit('consecutiveToolFailureLimit', e.target.value),
|
|
4648
4614
|
|
|
4649
4615
|
}),
|
|
4650
4616
|
|
|
@@ -4680,7 +4646,7 @@ window.__ModuleLoader__.load({
|
|
|
4680
4646
|
type: 'button',
|
|
4681
4647
|
className: 'dsh-goal-btn-inline-reset',
|
|
4682
4648
|
title: t('resetField') || 'Reset to default',
|
|
4683
|
-
onClick: () =>
|
|
4649
|
+
onClick: () => resetFieldToDefault('maxTokenBudget'),
|
|
4684
4650
|
},
|
|
4685
4651
|
React.createElement(IconRotateCcw, { size: 12 }),
|
|
4686
4652
|
t('resetField') || 'Default'
|
|
@@ -4697,7 +4663,7 @@ window.__ModuleLoader__.load({
|
|
|
4697
4663
|
value: budgetStatus.value !== undefined ? String(budgetStatus.value) : '',
|
|
4698
4664
|
disabled,
|
|
4699
4665
|
className: 'dsh-goal-card-input',
|
|
4700
|
-
onChange: (e) => edit('maxTokenBudget',
|
|
4666
|
+
onChange: (e) => edit('maxTokenBudget', e.target.value),
|
|
4701
4667
|
})
|
|
4702
4668
|
),
|
|
4703
4669
|
|
|
@@ -4721,7 +4687,7 @@ window.__ModuleLoader__.load({
|
|
|
4721
4687
|
type: 'button',
|
|
4722
4688
|
className: 'dsh-goal-btn-inline-reset',
|
|
4723
4689
|
title: t('resetField') || 'Reset to default',
|
|
4724
|
-
onClick: () =>
|
|
4690
|
+
onClick: () => resetFieldToDefault('budgetWarningThreshold'),
|
|
4725
4691
|
},
|
|
4726
4692
|
React.createElement(IconRotateCcw, { size: 12 }),
|
|
4727
4693
|
t('resetField') || 'Default'
|
|
@@ -4738,7 +4704,7 @@ window.__ModuleLoader__.load({
|
|
|
4738
4704
|
value: budgetWarnStatus.value !== undefined ? String(budgetWarnStatus.value) : '',
|
|
4739
4705
|
disabled,
|
|
4740
4706
|
className: 'dsh-goal-card-input',
|
|
4741
|
-
onChange: (e) => edit('budgetWarningThreshold',
|
|
4707
|
+
onChange: (e) => edit('budgetWarningThreshold', e.target.value),
|
|
4742
4708
|
})
|
|
4743
4709
|
),
|
|
4744
4710
|
|
|
@@ -5228,16 +5194,9 @@ window.__ModuleLoader__.load({
|
|
|
5228
5194
|
GoalSettingsCard,
|
|
5229
5195
|
);
|
|
5230
5196
|
|
|
5231
|
-
|
|
5232
|
-
|
|
5233
|
-
|
|
5234
|
-
name: 'settings.plugin.item',
|
|
5235
|
-
key: NS,
|
|
5236
|
-
locale: NS,
|
|
5237
|
-
inject: () => ({ ctx, scope: configForms }),
|
|
5238
|
-
},
|
|
5239
|
-
GoalSettingsCard,
|
|
5240
|
-
);
|
|
5197
|
+
// settings.plugin.item is gone: it was retired before DSH 0.1.7-rc.2, so
|
|
5198
|
+
// it only mounted GoalSettingsCard a second time on a seat that no longer
|
|
5199
|
+
// exists. plugins.item and plugins.row.config above cover both releases.
|
|
5241
5200
|
|
|
5242
5201
|
return () => {
|
|
5243
5202
|
for (const dispose of disposers) {
|
|
@@ -5305,16 +5264,8 @@ window.__ModuleLoader__.load({
|
|
|
5305
5264
|
GoalSettingsCard,
|
|
5306
5265
|
);
|
|
5307
5266
|
|
|
5308
|
-
|
|
5309
|
-
|
|
5310
|
-
{
|
|
5311
|
-
name: 'settings.plugin.item',
|
|
5312
|
-
key: NS,
|
|
5313
|
-
locale: NS,
|
|
5314
|
-
inject: () => ({ ctx, scope: configForms }),
|
|
5315
|
-
},
|
|
5316
|
-
GoalSettingsCard,
|
|
5317
|
-
);
|
|
5267
|
+
// settings.plugin.item is gone for the same reason: retired before DSH
|
|
5268
|
+
// 0.1.7-rc.2, it mounted GoalSettingsCard a second time on a dead seat.
|
|
5318
5269
|
}
|
|
5319
5270
|
};
|
|
5320
5271
|
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,17 @@
|
|
|
1
1
|
import path from 'node:path';
|
|
2
2
|
import os from 'node:os';
|
|
3
3
|
import z from '@deepseek-ai/schemastery';
|
|
4
|
+
// Polyfill volatile schema modifier if running against schemastery < 3.18.4
|
|
5
|
+
if (typeof z.Schema?.prototype?.volatile !== 'function') {
|
|
6
|
+
const proto = z.Schema ? z.Schema.prototype : (typeof z === 'function' ? Object.getPrototypeOf(z.string?.() || {}) : null);
|
|
7
|
+
if (proto && typeof proto.extra === 'function' && typeof proto.volatile !== 'function') {
|
|
8
|
+
proto.volatile = function volatile() {
|
|
9
|
+
if (this.meta?.volatile) throw new TypeError('volatile schema is already wrapped');
|
|
10
|
+
return this.extra('volatile', true);
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
4
15
|
import { AddonStore } from './addon-store.js';
|
|
5
16
|
import { TokenBudgetGuard } from './token-budget-guard.js';
|
|
6
17
|
import { GitSnapshotManager } from './git-snapshot-manager.js';
|
|
@@ -18,27 +29,38 @@ export const inject = ['webServer', 'settings'];
|
|
|
18
29
|
const NS = 'dsh-goal';
|
|
19
30
|
|
|
20
31
|
export const Config = z.object({
|
|
21
|
-
maxIterations: z.number().default(25).description('Safety limit: max autonomous iterations per goal'),
|
|
22
|
-
autoDrive: z.boolean().default(true).description('Automatically continue the loop after each turn'),
|
|
23
|
-
enableSound: z.boolean().default(true).description('Play synthesized audio chime on goal completion or failure'),
|
|
24
|
-
showQuickLaunchButton: z.boolean().default(true).description('Show quick launch goal button above composer dock'),
|
|
25
|
-
consecutiveToolFailureLimit: z.number().default(3).description('Auto-pause goal if N consecutive turns encounter tool execution errors (0 to disable)'),
|
|
26
|
-
enableBrowserNotifications: z.boolean().default(true).description('Show desktop notifications on goal completion or failure'),
|
|
27
|
-
maxTokenBudget: z.number().default(0).description('Maximum token budget per goal session (0 to disable)'),
|
|
28
|
-
budgetWarningThreshold: z.number().default(80).description('Percentage of token budget consumed before model warning injection (e.g. 80)'),
|
|
29
|
-
autoCheckpointOnMilestone: z.boolean().default(false).description('Automatically create git commit checkpoint upon milestone completion'),
|
|
30
|
-
enableLiveActivityFeed: z.boolean().default(true).description('Display compact recent tool and control activity feed in details modal'),
|
|
31
|
-
enablePreplanning: z.boolean().default(true).description('Generate initial draft plan for approval before launching autonomous turns'),
|
|
32
|
-
autoBranchOnGoalStart: z.boolean().default(true).description('Automatically create dedicated isolated git branch for goal execution'),
|
|
33
|
-
enablePostGoalRetrospective: z.boolean().default(true).description('Generate structured retrospective and analytics card upon goal completion'),
|
|
34
|
-
autoScaleBudgetNearCompletion: z.boolean().default(false).description('Automatically grant token buffer extension if budget reaches 100% near completion'),
|
|
35
|
-
enableTemplatesDrawer: z.boolean().default(true).description('Provide pre-configured engineering goal templates in UI drawer'),
|
|
36
|
-
enableMilestoneDependencies: z.boolean().default(true).description('Enforce milestone dependency ordering and block premature execution'),
|
|
37
|
-
soundScheme: z.string().default('default').description('Audio theme for status and milestone notifications'),
|
|
38
|
-
enableVoiceAnnouncements: z.boolean().default(false).description('Use Web Speech API for voice announcements on milestone events'),
|
|
39
|
-
storagePath: z.string().default('').description('Custom filesystem path for persistent state storage'),
|
|
32
|
+
maxIterations: z.number().default(25).volatile().description('Safety limit: max autonomous iterations per goal'),
|
|
33
|
+
autoDrive: z.boolean().default(true).volatile().description('Automatically continue the loop after each turn'),
|
|
34
|
+
enableSound: z.boolean().default(true).volatile().description('Play synthesized audio chime on goal completion or failure'),
|
|
35
|
+
showQuickLaunchButton: z.boolean().default(true).volatile().description('Show quick launch goal button above composer dock'),
|
|
36
|
+
consecutiveToolFailureLimit: z.number().default(3).volatile().description('Auto-pause goal if N consecutive turns encounter tool execution errors (0 to disable)'),
|
|
37
|
+
enableBrowserNotifications: z.boolean().default(true).volatile().description('Show desktop notifications on goal completion or failure'),
|
|
38
|
+
maxTokenBudget: z.number().default(0).volatile().description('Maximum token budget per goal session (0 to disable)'),
|
|
39
|
+
budgetWarningThreshold: z.number().default(80).volatile().description('Percentage of token budget consumed before model warning injection (e.g. 80)'),
|
|
40
|
+
autoCheckpointOnMilestone: z.boolean().default(false).volatile().description('Automatically create git commit checkpoint upon milestone completion'),
|
|
41
|
+
enableLiveActivityFeed: z.boolean().default(true).volatile().description('Display compact recent tool and control activity feed in details modal'),
|
|
42
|
+
enablePreplanning: z.boolean().default(true).volatile().description('Generate initial draft plan for approval before launching autonomous turns'),
|
|
43
|
+
autoBranchOnGoalStart: z.boolean().default(true).volatile().description('Automatically create dedicated isolated git branch for goal execution'),
|
|
44
|
+
enablePostGoalRetrospective: z.boolean().default(true).volatile().description('Generate structured retrospective and analytics card upon goal completion'),
|
|
45
|
+
autoScaleBudgetNearCompletion: z.boolean().default(false).volatile().description('Automatically grant token buffer extension if budget reaches 100% near completion'),
|
|
46
|
+
enableTemplatesDrawer: z.boolean().default(true).volatile().description('Provide pre-configured engineering goal templates in UI drawer'),
|
|
47
|
+
enableMilestoneDependencies: z.boolean().default(true).volatile().description('Enforce milestone dependency ordering and block premature execution'),
|
|
48
|
+
soundScheme: z.string().default('default').volatile().description('Audio theme for status and milestone notifications'),
|
|
49
|
+
enableVoiceAnnouncements: z.boolean().default(false).volatile().description('Use Web Speech API for voice announcements on milestone events'),
|
|
50
|
+
storagePath: z.string().default('').volatile().description('Custom filesystem path for persistent state storage'),
|
|
40
51
|
});
|
|
41
52
|
|
|
53
|
+
// DSH serves a namespace's settings form from the volatile fields of its profile
|
|
54
|
+
// entry schema, and a volatile field holds a Volatile box rather than its value.
|
|
55
|
+
// Unwrap before any caller reads one, and read lazily: the Loader mutates the
|
|
56
|
+
// boxes in place and re-announces them with loader/volatile-update.
|
|
57
|
+
export function plainConfig(value) {
|
|
58
|
+
if (value === null || typeof value !== 'object') return value
|
|
59
|
+
if (Array.isArray(value)) return value.map(plainConfig)
|
|
60
|
+
if (typeof value.get === 'function') return plainConfig(value.get())
|
|
61
|
+
return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, plainConfig(v)]))
|
|
62
|
+
}
|
|
63
|
+
|
|
42
64
|
/**
|
|
43
65
|
* Safely access a service from Cordis context without throwing if un-injected.
|
|
44
66
|
* @param {any} ctx
|
|
@@ -58,29 +80,29 @@ export function safeService(ctx, name) {
|
|
|
58
80
|
}
|
|
59
81
|
|
|
60
82
|
export function apply(ctx, config = {}) {
|
|
61
|
-
let settingsScope = null;
|
|
62
83
|
const sseClients = new Map();
|
|
84
|
+
const cfg = plainConfig(config || {});
|
|
63
85
|
|
|
64
86
|
let currentSettings = {
|
|
65
|
-
maxIterations:
|
|
66
|
-
autoDrive:
|
|
67
|
-
enableSound:
|
|
68
|
-
showQuickLaunchButton:
|
|
69
|
-
consecutiveToolFailureLimit:
|
|
70
|
-
enableBrowserNotifications:
|
|
71
|
-
maxTokenBudget:
|
|
72
|
-
budgetWarningThreshold:
|
|
73
|
-
autoCheckpointOnMilestone:
|
|
74
|
-
enableLiveActivityFeed:
|
|
75
|
-
enablePreplanning:
|
|
76
|
-
autoBranchOnGoalStart:
|
|
77
|
-
enablePostGoalRetrospective:
|
|
78
|
-
autoScaleBudgetNearCompletion:
|
|
79
|
-
enableTemplatesDrawer:
|
|
80
|
-
enableMilestoneDependencies:
|
|
81
|
-
soundScheme:
|
|
82
|
-
enableVoiceAnnouncements:
|
|
83
|
-
storagePath:
|
|
87
|
+
maxIterations: cfg?.maxIterations ?? 25,
|
|
88
|
+
autoDrive: cfg?.autoDrive ?? true,
|
|
89
|
+
enableSound: cfg?.enableSound ?? true,
|
|
90
|
+
showQuickLaunchButton: cfg?.showQuickLaunchButton ?? true,
|
|
91
|
+
consecutiveToolFailureLimit: cfg?.consecutiveToolFailureLimit ?? 3,
|
|
92
|
+
enableBrowserNotifications: cfg?.enableBrowserNotifications ?? true,
|
|
93
|
+
maxTokenBudget: cfg?.maxTokenBudget ?? 0,
|
|
94
|
+
budgetWarningThreshold: cfg?.budgetWarningThreshold ?? 80,
|
|
95
|
+
autoCheckpointOnMilestone: cfg?.autoCheckpointOnMilestone ?? false,
|
|
96
|
+
enableLiveActivityFeed: cfg?.enableLiveActivityFeed ?? true,
|
|
97
|
+
enablePreplanning: cfg?.enablePreplanning ?? true,
|
|
98
|
+
autoBranchOnGoalStart: cfg?.autoBranchOnGoalStart ?? true,
|
|
99
|
+
enablePostGoalRetrospective: cfg?.enablePostGoalRetrospective ?? true,
|
|
100
|
+
autoScaleBudgetNearCompletion: cfg?.autoScaleBudgetNearCompletion ?? false,
|
|
101
|
+
enableTemplatesDrawer: cfg?.enableTemplatesDrawer ?? true,
|
|
102
|
+
enableMilestoneDependencies: cfg?.enableMilestoneDependencies ?? true,
|
|
103
|
+
soundScheme: cfg?.soundScheme ?? 'default',
|
|
104
|
+
enableVoiceAnnouncements: cfg?.enableVoiceAnnouncements ?? false,
|
|
105
|
+
storagePath: cfg?.storagePath,
|
|
84
106
|
};
|
|
85
107
|
|
|
86
108
|
const defaultStorageDir = process.env.DSH_HOME || path.join(os.homedir(), '.dsh');
|
|
@@ -288,33 +310,45 @@ export function apply(ctx, config = {}) {
|
|
|
288
310
|
if (typeof patch.storagePath === 'string' && patch.storagePath.trim()) {
|
|
289
311
|
addonStore.setStoragePath(patch.storagePath.trim());
|
|
290
312
|
}
|
|
313
|
+
if (fallbackEngine && typeof fallbackEngine.updateConfig === 'function') {
|
|
314
|
+
fallbackEngine.updateConfig({
|
|
315
|
+
defaultMaxIterations: currentSettings.maxIterations,
|
|
316
|
+
autoDrive: currentSettings.autoDrive,
|
|
317
|
+
consecutiveToolFailureLimit: currentSettings.consecutiveToolFailureLimit,
|
|
318
|
+
maxTokenBudget: currentSettings.maxTokenBudget,
|
|
319
|
+
budgetWarningThreshold: currentSettings.budgetWarningThreshold,
|
|
320
|
+
autoCheckpointOnMilestone: currentSettings.autoCheckpointOnMilestone,
|
|
321
|
+
enableLiveActivityFeed: currentSettings.enableLiveActivityFeed,
|
|
322
|
+
enablePreplanning: currentSettings.enablePreplanning,
|
|
323
|
+
autoBranchOnGoalStart: currentSettings.autoBranchOnGoalStart,
|
|
324
|
+
enablePostGoalRetrospective: currentSettings.enablePostGoalRetrospective,
|
|
325
|
+
autoScaleBudgetNearCompletion: currentSettings.autoScaleBudgetNearCompletion,
|
|
326
|
+
enableTemplatesDrawer: currentSettings.enableTemplatesDrawer,
|
|
327
|
+
enableMilestoneDependencies: currentSettings.enableMilestoneDependencies,
|
|
328
|
+
});
|
|
329
|
+
}
|
|
291
330
|
};
|
|
292
331
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (typeof off === 'function') off();
|
|
309
|
-
settingsScope = null;
|
|
310
|
-
};
|
|
311
|
-
}, 'dsh-goal: settings subscription');
|
|
312
|
-
}
|
|
332
|
+
// DSH hands the plugin the entry config, whose .volatile() fields hold Volatile
|
|
333
|
+
// boxes. The settings service re-merges that config and announces it with
|
|
334
|
+
// loader/volatile-update, which both 0.1.7-rc.2 and 0.2.0 emit. settings.register
|
|
335
|
+
// exists in neither release, so the scope it returned never existed and every
|
|
336
|
+
// setting changed in the card had no effect on the running engine.
|
|
337
|
+
const readEntrySettings = () => plainConfig(config || {});
|
|
338
|
+
applySettings(readEntrySettings());
|
|
339
|
+
|
|
340
|
+
if (typeof ctx?.on === 'function') {
|
|
341
|
+
try {
|
|
342
|
+
ctx.on('loader/volatile-update', () => {
|
|
343
|
+
try {
|
|
344
|
+
applySettings(readEntrySettings());
|
|
345
|
+
} catch (err) {
|
|
346
|
+
logger.warn?.('dsh-goal: settings refresh failed:', err?.message || err);
|
|
313
347
|
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
}
|
|
348
|
+
});
|
|
349
|
+
} catch (err) {
|
|
350
|
+
logger.warn?.('dsh-goal: volatile-update subscription failed:', err?.message || err);
|
|
351
|
+
}
|
|
318
352
|
}
|
|
319
353
|
|
|
320
354
|
if (typeof ctx?.effect === 'function') {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-goal",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.18",
|
|
4
4
|
"description": "Core Goal Addon (Token Budget, Milestones, Git Snapshots & Retrospective Reports) for DeepSeek Harness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -55,14 +55,14 @@
|
|
|
55
55
|
"peerDependencies": {
|
|
56
56
|
"@deepseek-ai/cordis": "^4.0.1",
|
|
57
57
|
"@deepseek-ai/dsh-host-webserver": "^0.1.7-rc.2 || ^0.2.0-rc.1",
|
|
58
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
58
|
+
"@deepseek-ai/schemastery": "^3.18.4",
|
|
59
59
|
"@deepseek-ai/dsh-goal": "^0.1.7-rc.2 || ^0.2.0-rc.1"
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
62
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
63
63
|
},
|
|
64
64
|
"dependencies": {
|
|
65
|
-
"@deepseek-ai/schemastery": "^3.18.
|
|
65
|
+
"@deepseek-ai/schemastery": "^3.18.4"
|
|
66
66
|
},
|
|
67
67
|
"peerDependenciesMeta": {
|
|
68
68
|
"@deepseek-ai/dsh-goal": {
|