@worca/ui 0.28.0-rc.1 → 0.29.0
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/app/main.bundle.js +1122 -980
- package/app/main.bundle.js.map +3 -3
- package/app/styles.css +27 -4
- package/package.json +1 -1
- package/server/dispatch-defaults.js +16 -1
- package/server/dispatch-migration.js +180 -47
- package/server/settings-validator.js +59 -0
package/app/styles.css
CHANGED
|
@@ -1978,14 +1978,12 @@ sl-input [slot="prefix"] {
|
|
|
1978
1978
|
}
|
|
1979
1979
|
|
|
1980
1980
|
.dispatch-chip-locked {
|
|
1981
|
-
text-decoration: line-through;
|
|
1982
1981
|
opacity: 0.6;
|
|
1983
1982
|
}
|
|
1984
1983
|
|
|
1985
|
-
/* Auto-included meta-tool chips (Skill, Agent) — locked but
|
|
1986
|
-
* the
|
|
1984
|
+
/* Auto-included meta-tool chips (Skill, Agent) — locked but visually distinct
|
|
1985
|
+
* via the dashed border; present-and-required rather than blocked. */
|
|
1987
1986
|
.dispatch-chip-auto {
|
|
1988
|
-
text-decoration: none;
|
|
1989
1987
|
opacity: 0.75;
|
|
1990
1988
|
font-style: italic;
|
|
1991
1989
|
border-style: dashed;
|
|
@@ -2005,6 +2003,16 @@ sl-input [slot="prefix"] {
|
|
|
2005
2003
|
font-size: 10px;
|
|
2006
2004
|
}
|
|
2007
2005
|
|
|
2006
|
+
/* Inherits-defaults placeholder — shown when per-agent entry is an empty
|
|
2007
|
+
* list, which the resolver treats as fall-through to _defaults. Visually
|
|
2008
|
+
* lighter than lockdown so the two states are easy to distinguish. */
|
|
2009
|
+
.dispatch-chip-inherits {
|
|
2010
|
+
text-decoration: none;
|
|
2011
|
+
opacity: 0.6;
|
|
2012
|
+
font-style: italic;
|
|
2013
|
+
font-size: 10px;
|
|
2014
|
+
}
|
|
2015
|
+
|
|
2008
2016
|
.dispatch-chip-warn {
|
|
2009
2017
|
--sl-color-neutral-200: var(--sl-color-warning-200);
|
|
2010
2018
|
background: var(--sl-color-warning-100);
|
|
@@ -2015,6 +2023,21 @@ sl-input [slot="prefix"] {
|
|
|
2015
2023
|
margin-bottom: 16px;
|
|
2016
2024
|
}
|
|
2017
2025
|
|
|
2026
|
+
/* Header row: optional section title on the left, per-section Reset on the
|
|
2027
|
+
* right. Rendered at the top of each dispatch panel body (visible only when the
|
|
2028
|
+
* sl-details is expanded). */
|
|
2029
|
+
.dispatch-section-header {
|
|
2030
|
+
display: flex;
|
|
2031
|
+
align-items: center;
|
|
2032
|
+
justify-content: space-between;
|
|
2033
|
+
gap: 8px;
|
|
2034
|
+
min-height: 28px;
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
.dispatch-section-reset {
|
|
2038
|
+
margin-left: auto;
|
|
2039
|
+
}
|
|
2040
|
+
|
|
2018
2041
|
.dispatch-section-title {
|
|
2019
2042
|
font-size: 14px;
|
|
2020
2043
|
font-weight: 600;
|
package/package.json
CHANGED
|
@@ -18,7 +18,22 @@ export const DISPATCH_DEFAULTS = {
|
|
|
18
18
|
'fewer-permission-prompts',
|
|
19
19
|
'loop',
|
|
20
20
|
'schedule',
|
|
21
|
-
|
|
21
|
+
// worca-* dev skills that genuinely must stay off-limits to pipeline
|
|
22
|
+
// agents: release/publish, PR merges, cross-repo sync, installation,
|
|
23
|
+
// agent/governance override (privilege escalation), pipeline launch
|
|
24
|
+
// (recursion), and autonomous issue/plan creation. The rest of the
|
|
25
|
+
// worca-* dev tooling (precommit, coverage, ui/event scaffolding,
|
|
26
|
+
// webhook-test, issue read) is allowed via the per-agent '*' wildcard.
|
|
27
|
+
'worca-release',
|
|
28
|
+
'worca-rc',
|
|
29
|
+
'worca-pr-prep',
|
|
30
|
+
'worca-install',
|
|
31
|
+
'worca-sync',
|
|
32
|
+
'worca-sync-commit',
|
|
33
|
+
'worca-sync-pr',
|
|
34
|
+
'worca-agent-override',
|
|
35
|
+
'worca-analyze',
|
|
36
|
+
'worca-plan-new',
|
|
22
37
|
'update-config',
|
|
23
38
|
'hookify:hookify',
|
|
24
39
|
'hookify:configure',
|
|
@@ -55,12 +55,141 @@ function _absorbFlatDispatchKeys(dispatch) {
|
|
|
55
55
|
return true;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
// --- One-time dispatch-default normalization (W-054 follow-up) -------------
|
|
59
|
+
//
|
|
60
|
+
// Mirror of normalize_dispatch_defaults() in src/worca/hooks/tracking.py.
|
|
61
|
+
// Bumped when a new one-time normalization is added; stamped onto
|
|
62
|
+
// governance.dispatch_migration_version so it runs exactly once per config.
|
|
63
|
+
export const DISPATCH_MIGRATION_VERSION = 1;
|
|
64
|
+
|
|
65
|
+
// Pre-W-054 (W-038-era) shipped subagent default: every pipeline agent capped
|
|
66
|
+
// to Explore-only. coordinator:[] / empty lists fall through to _defaults and
|
|
67
|
+
// are ignored in the comparison.
|
|
68
|
+
const _LEGACY_EXPLORE_SUBAGENT_DEFAULT = {
|
|
69
|
+
planner: ['Explore'],
|
|
70
|
+
implementer: ['Explore'],
|
|
71
|
+
tester: ['Explore'],
|
|
72
|
+
guardian: ['Explore'],
|
|
73
|
+
reviewer: ['Explore'],
|
|
74
|
+
plan_reviewer: ['Explore'],
|
|
75
|
+
learner: ['Explore'],
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// Pre-narrowing skills denylist (carried the broad `worca-*` glob).
|
|
79
|
+
const _LEGACY_SKILLS_ALWAYS_DISALLOWED = new Set([
|
|
80
|
+
'batch',
|
|
81
|
+
'fewer-permission-prompts',
|
|
82
|
+
'loop',
|
|
83
|
+
'schedule',
|
|
84
|
+
'worca-*',
|
|
85
|
+
'update-config',
|
|
86
|
+
'hookify:hookify',
|
|
87
|
+
'hookify:configure',
|
|
88
|
+
'hookify:list',
|
|
89
|
+
'hookify:writing-rules',
|
|
90
|
+
'init',
|
|
91
|
+
]);
|
|
92
|
+
|
|
93
|
+
function _canonicalPerAgent(perAgent) {
|
|
94
|
+
const out = {};
|
|
95
|
+
for (const [agent, allow] of Object.entries(perAgent)) {
|
|
96
|
+
if (agent === '_defaults') continue;
|
|
97
|
+
if (!Array.isArray(allow) || allow.length === 0) continue;
|
|
98
|
+
out[agent] = [...allow].sort();
|
|
99
|
+
}
|
|
100
|
+
return out;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function _sameStringMap(a, b) {
|
|
104
|
+
const ak = Object.keys(a);
|
|
105
|
+
const bk = Object.keys(b);
|
|
106
|
+
if (ak.length !== bk.length) return false;
|
|
107
|
+
for (const k of ak) {
|
|
108
|
+
const av = a[k];
|
|
109
|
+
const bv = b[k];
|
|
110
|
+
if (!Array.isArray(bv) || av.length !== bv.length) return false;
|
|
111
|
+
for (let i = 0; i < av.length; i++) {
|
|
112
|
+
if (av[i] !== bv[i]) return false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Collapse a stale Explore-only per_agent_allow to the new `_defaults: ["*"]`
|
|
120
|
+
* default. Only fires on the untouched W-038 shape with an unset/wildcard
|
|
121
|
+
* _defaults. Returns true if changed.
|
|
122
|
+
*/
|
|
123
|
+
export function adoptStaleSubagentDefault(subagentsCfg) {
|
|
124
|
+
if (!subagentsCfg || typeof subagentsCfg !== 'object') return false;
|
|
125
|
+
const pa = subagentsCfg.per_agent_allow;
|
|
126
|
+
if (!pa || typeof pa !== 'object' || Array.isArray(pa)) return false;
|
|
127
|
+
const def = pa._defaults;
|
|
128
|
+
const defOk =
|
|
129
|
+
def === undefined ||
|
|
130
|
+
(Array.isArray(def) && def.length === 1 && def[0] === '*');
|
|
131
|
+
if (!defOk) return false;
|
|
132
|
+
const expected = _canonicalPerAgent(_LEGACY_EXPLORE_SUBAGENT_DEFAULT);
|
|
133
|
+
if (!_sameStringMap(_canonicalPerAgent(pa), expected)) return false;
|
|
134
|
+
subagentsCfg.per_agent_allow = { _defaults: ['*'] };
|
|
135
|
+
return true;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Widen an untouched skills denylist (broad `worca-*`) to the current set.
|
|
140
|
+
* Exact-match (set) guarded. Returns true if changed.
|
|
141
|
+
*/
|
|
142
|
+
export function adoptNarrowedSkillsDenylist(skillsCfg) {
|
|
143
|
+
if (!skillsCfg || typeof skillsCfg !== 'object') return false;
|
|
144
|
+
const current = skillsCfg.always_disallowed;
|
|
145
|
+
if (!Array.isArray(current)) return false;
|
|
146
|
+
if (current.length !== _LEGACY_SKILLS_ALWAYS_DISALLOWED.size) return false;
|
|
147
|
+
for (const item of current) {
|
|
148
|
+
if (!_LEGACY_SKILLS_ALWAYS_DISALLOWED.has(item)) return false;
|
|
149
|
+
}
|
|
150
|
+
skillsCfg.always_disallowed = [...DISPATCH_DEFAULTS.skills.always_disallowed];
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Apply one-time dispatch-default normalizations, gated by a version stamp.
|
|
156
|
+
* Brings an *untouched* config up to current shipped defaults for the two
|
|
157
|
+
* things that changed after W-054 (subagent per_agent_allow, skills denylist).
|
|
158
|
+
* Mutates governanceCfg; returns change descriptions.
|
|
159
|
+
*/
|
|
160
|
+
export function normalizeDispatchDefaults(governanceCfg) {
|
|
161
|
+
const changes = [];
|
|
162
|
+
if (!governanceCfg || typeof governanceCfg !== 'object') return changes;
|
|
163
|
+
let stamp = governanceCfg.dispatch_migration_version;
|
|
164
|
+
if (!Number.isInteger(stamp)) stamp = 0;
|
|
165
|
+
if (stamp >= DISPATCH_MIGRATION_VERSION) return changes;
|
|
166
|
+
const dispatch = governanceCfg.dispatch;
|
|
167
|
+
if (!dispatch || typeof dispatch !== 'object' || Array.isArray(dispatch)) {
|
|
168
|
+
return changes;
|
|
169
|
+
}
|
|
170
|
+
if (adoptStaleSubagentDefault(dispatch.subagents)) {
|
|
171
|
+
changes.push(
|
|
172
|
+
'governance.dispatch.subagents: adopted new default (_defaults:["*"]) for config pinned to legacy Explore-only set',
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
if (adoptNarrowedSkillsDenylist(dispatch.skills)) {
|
|
176
|
+
changes.push(
|
|
177
|
+
'governance.dispatch.skills.always_disallowed: narrowed legacy "worca-*" glob to the current must-disallow set',
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
governanceCfg.dispatch_migration_version = DISPATCH_MIGRATION_VERSION;
|
|
181
|
+
return changes;
|
|
182
|
+
}
|
|
183
|
+
|
|
58
184
|
/**
|
|
59
185
|
* Migrate legacy governance.subagent_dispatch and/or legacy flat
|
|
60
|
-
* governance.dispatch (agent-keyed) → governance.dispatch.subagents.per_agent_allow
|
|
186
|
+
* governance.dispatch (agent-keyed) → governance.dispatch.subagents.per_agent_allow,
|
|
187
|
+
* then apply the one-time dispatch-default normalization.
|
|
61
188
|
*
|
|
62
|
-
* Seeds _defaults, adds tools/skills defaults, drops _dispatch_legacy.
|
|
63
|
-
*
|
|
189
|
+
* Seeds _defaults, adds tools/skills defaults, drops _dispatch_legacy. The
|
|
190
|
+
* normalization runs even with no legacy shape so already-migrated configs
|
|
191
|
+
* pinned to the stale Explore-only subagent default (or the broad `worca-*`
|
|
192
|
+
* skills glob) self-heal on next save. Gated by a version stamp → idempotent.
|
|
64
193
|
*
|
|
65
194
|
* @param {object} worcaConfig — the `worca` object from settings (mutated)
|
|
66
195
|
* @returns {string[]} list of change descriptions (empty = no-op)
|
|
@@ -73,60 +202,64 @@ export function migrateDispatchGovernance(worcaConfig) {
|
|
|
73
202
|
const hasSubagentDispatch = 'subagent_dispatch' in gov;
|
|
74
203
|
const hasLegacyFlatDispatch = _isLegacyFlatDispatch(gov.dispatch);
|
|
75
204
|
|
|
76
|
-
if (
|
|
205
|
+
if (hasSubagentDispatch || hasLegacyFlatDispatch) {
|
|
206
|
+
if (!gov.dispatch || Array.isArray(gov.dispatch)) gov.dispatch = {};
|
|
207
|
+
const dispatch = gov.dispatch;
|
|
77
208
|
|
|
78
|
-
|
|
79
|
-
|
|
209
|
+
// Absorb legacy flat shape (pre-W-038) first so subagent_dispatch values
|
|
210
|
+
// take precedence below.
|
|
211
|
+
if (hasLegacyFlatDispatch) {
|
|
212
|
+
_absorbFlatDispatchKeys(dispatch);
|
|
213
|
+
changes.push(
|
|
214
|
+
'governance.dispatch (flat agent-keyed) -> governance.dispatch.subagents (W-054)',
|
|
215
|
+
);
|
|
216
|
+
}
|
|
80
217
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
218
|
+
if (hasSubagentDispatch) {
|
|
219
|
+
const old = gov.subagent_dispatch;
|
|
220
|
+
delete gov.subagent_dispatch;
|
|
221
|
+
if (!dispatch.subagents) dispatch.subagents = {};
|
|
222
|
+
if (!dispatch.subagents.per_agent_allow) {
|
|
223
|
+
dispatch.subagents.per_agent_allow = {};
|
|
224
|
+
}
|
|
225
|
+
Object.assign(dispatch.subagents.per_agent_allow, old);
|
|
226
|
+
changes.push(
|
|
227
|
+
'governance.subagent_dispatch -> governance.dispatch.subagents (W-054)',
|
|
228
|
+
);
|
|
229
|
+
}
|
|
89
230
|
|
|
90
|
-
if (hasSubagentDispatch) {
|
|
91
|
-
const old = gov.subagent_dispatch;
|
|
92
|
-
delete gov.subagent_dispatch;
|
|
93
231
|
if (!dispatch.subagents) dispatch.subagents = {};
|
|
94
|
-
|
|
95
|
-
dispatch.subagents.per_agent_allow = {};
|
|
96
|
-
}
|
|
97
|
-
Object.assign(dispatch.subagents.per_agent_allow, old);
|
|
98
|
-
changes.push(
|
|
99
|
-
'governance.subagent_dispatch -> governance.dispatch.subagents (W-054)',
|
|
100
|
-
);
|
|
101
|
-
}
|
|
232
|
+
const subagents = dispatch.subagents;
|
|
102
233
|
|
|
103
|
-
|
|
104
|
-
|
|
234
|
+
if (!subagents.per_agent_allow) subagents.per_agent_allow = {};
|
|
235
|
+
if (!('_defaults' in subagents.per_agent_allow)) {
|
|
236
|
+
subagents.per_agent_allow._defaults = [
|
|
237
|
+
...DISPATCH_DEFAULTS.subagents.per_agent_allow._defaults,
|
|
238
|
+
];
|
|
239
|
+
}
|
|
105
240
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
241
|
+
if (!subagents.always_disallowed) {
|
|
242
|
+
subagents.always_disallowed = [
|
|
243
|
+
...DISPATCH_DEFAULTS.subagents.always_disallowed,
|
|
244
|
+
];
|
|
245
|
+
}
|
|
246
|
+
if (!subagents.default_denied) {
|
|
247
|
+
subagents.default_denied = [
|
|
248
|
+
...DISPATCH_DEFAULTS.subagents.default_denied,
|
|
249
|
+
];
|
|
250
|
+
}
|
|
112
251
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
subagents.default_denied = [...DISPATCH_DEFAULTS.subagents.default_denied];
|
|
120
|
-
}
|
|
252
|
+
if (!dispatch.tools) {
|
|
253
|
+
dispatch.tools = structuredClone(DISPATCH_DEFAULTS.tools);
|
|
254
|
+
}
|
|
255
|
+
if (!dispatch.skills) {
|
|
256
|
+
dispatch.skills = structuredClone(DISPATCH_DEFAULTS.skills);
|
|
257
|
+
}
|
|
121
258
|
|
|
122
|
-
|
|
123
|
-
dispatch.tools = structuredClone(DISPATCH_DEFAULTS.tools);
|
|
124
|
-
}
|
|
125
|
-
if (!dispatch.skills) {
|
|
126
|
-
dispatch.skills = structuredClone(DISPATCH_DEFAULTS.skills);
|
|
259
|
+
delete gov._dispatch_legacy;
|
|
127
260
|
}
|
|
128
261
|
|
|
129
|
-
|
|
262
|
+
changes.push(...normalizeDispatchDefaults(gov));
|
|
130
263
|
|
|
131
264
|
return changes;
|
|
132
265
|
}
|
|
@@ -21,6 +21,9 @@ const VALID_LOOPS = [
|
|
|
21
21
|
'restart_planning',
|
|
22
22
|
'plan_review',
|
|
23
23
|
];
|
|
24
|
+
const VALID_EFFORT_RUNGS = ['low', 'medium', 'high', 'xhigh', 'max'];
|
|
25
|
+
const VALID_AUTO_MODES = ['disabled', 'reactive', 'adaptive'];
|
|
26
|
+
const VALID_EFFORT_KEYS = ['auto_mode', 'auto_cap'];
|
|
24
27
|
const VALID_MILESTONES = ['plan_approval', 'pr_approval', 'deploy_approval'];
|
|
25
28
|
const VALID_GUARDS = [
|
|
26
29
|
'block_rm_rf',
|
|
@@ -92,6 +95,52 @@ export function validateSettingsPayload(body, options = {}) {
|
|
|
92
95
|
);
|
|
93
96
|
}
|
|
94
97
|
}
|
|
98
|
+
if (cfg.effort !== undefined) {
|
|
99
|
+
if (
|
|
100
|
+
typeof cfg.effort !== 'string' ||
|
|
101
|
+
!VALID_EFFORT_RUNGS.includes(cfg.effort)
|
|
102
|
+
) {
|
|
103
|
+
details.push(
|
|
104
|
+
`Invalid effort "${cfg.effort}" for agent "${name}". Must be one of: ${VALID_EFFORT_RUNGS.join(', ')}`,
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// effort
|
|
113
|
+
if (w.effort !== undefined) {
|
|
114
|
+
if (
|
|
115
|
+
typeof w.effort !== 'object' ||
|
|
116
|
+
w.effort === null ||
|
|
117
|
+
Array.isArray(w.effort)
|
|
118
|
+
) {
|
|
119
|
+
details.push('effort must be an object');
|
|
120
|
+
} else {
|
|
121
|
+
const ef = w.effort;
|
|
122
|
+
for (const key of Object.keys(ef)) {
|
|
123
|
+
if (!VALID_EFFORT_KEYS.includes(key)) {
|
|
124
|
+
details.push(`Unknown effort key: "${key}"`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (
|
|
128
|
+
ef.auto_mode !== undefined &&
|
|
129
|
+
(typeof ef.auto_mode !== 'string' ||
|
|
130
|
+
!VALID_AUTO_MODES.includes(ef.auto_mode))
|
|
131
|
+
) {
|
|
132
|
+
details.push(
|
|
133
|
+
`effort.auto_mode must be one of: ${VALID_AUTO_MODES.join(', ')}`,
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (
|
|
137
|
+
ef.auto_cap !== undefined &&
|
|
138
|
+
(typeof ef.auto_cap !== 'string' ||
|
|
139
|
+
!VALID_EFFORT_RUNGS.includes(ef.auto_cap))
|
|
140
|
+
) {
|
|
141
|
+
details.push(
|
|
142
|
+
`effort.auto_cap must be one of: ${VALID_EFFORT_RUNGS.join(', ')}`,
|
|
143
|
+
);
|
|
95
144
|
}
|
|
96
145
|
}
|
|
97
146
|
}
|
|
@@ -388,6 +437,16 @@ export function validateSettingsPayload(body, options = {}) {
|
|
|
388
437
|
);
|
|
389
438
|
}
|
|
390
439
|
}
|
|
440
|
+
if (g.dispatch_migration_version !== undefined) {
|
|
441
|
+
if (
|
|
442
|
+
!Number.isInteger(g.dispatch_migration_version) ||
|
|
443
|
+
g.dispatch_migration_version < 0
|
|
444
|
+
) {
|
|
445
|
+
details.push(
|
|
446
|
+
'dispatch_migration_version must be a non-negative integer',
|
|
447
|
+
);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
391
450
|
if (g.dispatch !== undefined) {
|
|
392
451
|
if (
|
|
393
452
|
typeof g.dispatch !== 'object' ||
|