@sienklogic/plan-build-run 2.30.0 → 2.31.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/CHANGELOG.md +14 -0
- package/dashboard/public/css/timeline.css +240 -0
- package/dashboard/src/components/Layout.tsx +3 -0
- package/dashboard/src/components/settings/ConfigEditor.tsx +399 -0
- package/dashboard/src/components/settings/SettingsPage.tsx +44 -0
- package/dashboard/src/components/timeline/AnalyticsPanel.tsx +99 -0
- package/dashboard/src/components/timeline/DependencyGraph.tsx +23 -0
- package/dashboard/src/components/timeline/TimelinePage.tsx +124 -0
- package/dashboard/src/index.tsx +2 -0
- package/dashboard/src/routes/timeline.routes.tsx +50 -0
- package/dashboard/src/services/analytics.service.d.ts +24 -0
- package/dashboard/src/services/local-llm-metrics.service.d.ts +26 -0
- package/dashboard/src/services/timeline.service.d.ts +20 -0
- package/dashboard/src/services/timeline.service.js +174 -0
- package/package.json +1 -1
- package/plugins/copilot-pbr/plugin.json +1 -1
- package/plugins/cursor-pbr/.cursor-plugin/plugin.json +1 -1
- package/plugins/pbr/.claude-plugin/plugin.json +1 -1
- package/plugins/pbr/scripts/config-schema.json +12 -0
- package/plugins/pbr/scripts/context-budget-check.js +4 -1
- package/plugins/pbr/scripts/enforce-pbr-workflow.js +218 -0
- package/plugins/pbr/scripts/pre-bash-dispatch.js +7 -0
- package/plugins/pbr/scripts/pre-write-dispatch.js +30 -18
- package/plugins/pbr/scripts/progress-tracker.js +1 -1
- package/plugins/pbr/scripts/validate-task.js +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,20 @@ All notable changes to Plan-Build-Run will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [2.31.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.30.0...plan-build-run-v2.31.0) (2026-02-24)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Features
|
|
12
|
+
|
|
13
|
+
* **41-01:** create timeline routes, wire into app, link timeline CSS in Layout ([ffc71c6](https://github.com/SienkLogic/plan-build-run/commit/ffc71c6ff32f72cf09894585e96cfd47d9ebad93))
|
|
14
|
+
* **41-01:** create timeline.service.js with event aggregation and filtering ([f585c2b](https://github.com/SienkLogic/plan-build-run/commit/f585c2b28a35ca028a0dfd6fb98fc3f717b0e42d))
|
|
15
|
+
* **41-01:** create TimelinePage component, EventStreamFragment, and timeline CSS ([105b3d3](https://github.com/SienkLogic/plan-build-run/commit/105b3d3e1fc3ea1f0f71c80301056e7a5ec0b125))
|
|
16
|
+
* **41-02:** add analytics and dependency-graph routes; refactor TimelinePage with section tabs ([4998f40](https://github.com/SienkLogic/plan-build-run/commit/4998f4093097953ca1e1e880413be60e07da08b4))
|
|
17
|
+
* **41-02:** add analytics/graph CSS sections and Mermaid CDN to Layout ([95d8853](https://github.com/SienkLogic/plan-build-run/commit/95d88536e406894021e454bb874d9dcc17abd179))
|
|
18
|
+
* **41-02:** add AnalyticsPanel and DependencyGraph components ([5bb080b](https://github.com/SienkLogic/plan-build-run/commit/5bb080bb363a3e2b56f5e2f2054c15e80dc6be23))
|
|
19
|
+
* **42-01:** create SettingsPage shell and ConfigEditor component (form + raw JSON modes) ([98caf06](https://github.com/SienkLogic/plan-build-run/commit/98caf0651314b4185b9c0e851d1607889956cb7f))
|
|
20
|
+
* **quick-008:** inject PBR workflow directive into SessionStart and PreCompact hooks ([c07574e](https://github.com/SienkLogic/plan-build-run/commit/c07574e32365c18e8c9239b8e4398330ba441182))
|
|
21
|
+
|
|
8
22
|
## [2.30.0](https://github.com/SienkLogic/plan-build-run/compare/plan-build-run-v2.29.0...plan-build-run-v2.30.0) (2026-02-24)
|
|
9
23
|
|
|
10
24
|
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* timeline.css — Timeline page styles
|
|
3
|
+
* Depends on tokens.css for design tokens (--space-*, --color-*, --radius-*, --font-mono)
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
.timeline {
|
|
7
|
+
display: flex;
|
|
8
|
+
flex-direction: column;
|
|
9
|
+
padding: var(--space-lg);
|
|
10
|
+
max-width: 1200px;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.timeline__filters {
|
|
14
|
+
display: flex;
|
|
15
|
+
flex-direction: row;
|
|
16
|
+
flex-wrap: wrap;
|
|
17
|
+
gap: var(--space-sm);
|
|
18
|
+
align-items: center;
|
|
19
|
+
padding: var(--space-md);
|
|
20
|
+
background: var(--color-surface);
|
|
21
|
+
border-radius: var(--radius-md);
|
|
22
|
+
margin-bottom: var(--space-lg);
|
|
23
|
+
border: 1px solid var(--color-border);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
.timeline__filters label {
|
|
27
|
+
display: flex;
|
|
28
|
+
align-items: center;
|
|
29
|
+
gap: var(--space-xs);
|
|
30
|
+
cursor: pointer;
|
|
31
|
+
font-size: 0.875rem;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
.timeline__stream {
|
|
35
|
+
position: relative;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
.timeline__list {
|
|
39
|
+
list-style: none;
|
|
40
|
+
padding: 0;
|
|
41
|
+
margin: 0;
|
|
42
|
+
display: flex;
|
|
43
|
+
flex-direction: column;
|
|
44
|
+
gap: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.timeline__event {
|
|
48
|
+
display: grid;
|
|
49
|
+
grid-template-columns: 1rem 10rem 9rem 1fr auto;
|
|
50
|
+
align-items: start;
|
|
51
|
+
gap: var(--space-sm);
|
|
52
|
+
padding: var(--space-sm) 0;
|
|
53
|
+
border-bottom: 1px solid var(--color-border);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.timeline__event-dot {
|
|
57
|
+
width: 0.5rem;
|
|
58
|
+
height: 0.5rem;
|
|
59
|
+
border-radius: 50%;
|
|
60
|
+
background: var(--color-accent);
|
|
61
|
+
margin-top: 0.35rem;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
.timeline__event--commit .timeline__event-dot {
|
|
65
|
+
background: var(--color-info, #3b82f6);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.timeline__event--todo-completion .timeline__event-dot {
|
|
69
|
+
background: var(--color-success, #22c55e);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.timeline__event--phase-transition .timeline__event-dot {
|
|
73
|
+
background: var(--color-warning, #f59e0b);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.timeline__event-time {
|
|
77
|
+
font-family: var(--font-mono);
|
|
78
|
+
font-size: 0.8rem;
|
|
79
|
+
color: var(--color-text-dim);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.timeline__event-type {
|
|
83
|
+
font-size: 0.75rem;
|
|
84
|
+
padding: 0.1rem 0.4rem;
|
|
85
|
+
border-radius: var(--radius-sm);
|
|
86
|
+
background: var(--color-surface);
|
|
87
|
+
border: 1px solid var(--color-border);
|
|
88
|
+
color: var(--color-text-dim);
|
|
89
|
+
white-space: nowrap;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.timeline__event-title {
|
|
93
|
+
font-size: 0.9rem;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.timeline__event-author {
|
|
97
|
+
font-size: 0.8rem;
|
|
98
|
+
color: var(--color-text-dim);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.timeline__loading {
|
|
102
|
+
color: var(--color-text-dim);
|
|
103
|
+
padding: var(--space-md);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.timeline__empty {
|
|
107
|
+
color: var(--color-text-dim);
|
|
108
|
+
padding: var(--space-lg);
|
|
109
|
+
text-align: center;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
.htmx-indicator {
|
|
113
|
+
opacity: 0;
|
|
114
|
+
transition: opacity 0.2s;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
.htmx-request .htmx-indicator,
|
|
118
|
+
.htmx-request.htmx-indicator {
|
|
119
|
+
opacity: 1;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/* ---- Section tabs ---- */
|
|
123
|
+
.timeline__section-tabs {
|
|
124
|
+
display: flex;
|
|
125
|
+
gap: 0;
|
|
126
|
+
border-bottom: 1px solid var(--color-border);
|
|
127
|
+
margin-bottom: var(--space-lg);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
.timeline__section-tab {
|
|
131
|
+
padding: var(--space-sm) var(--space-md);
|
|
132
|
+
border: none;
|
|
133
|
+
border-bottom: 2px solid transparent;
|
|
134
|
+
background: none;
|
|
135
|
+
cursor: pointer;
|
|
136
|
+
color: var(--color-text-dim);
|
|
137
|
+
font-size: 0.9rem;
|
|
138
|
+
font-family: var(--font-sans);
|
|
139
|
+
transition: all 0.15s;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.timeline__section-tab:hover { color: var(--color-text); }
|
|
143
|
+
|
|
144
|
+
.timeline__section-tab[aria-selected="true"] {
|
|
145
|
+
color: var(--color-accent);
|
|
146
|
+
border-bottom-color: var(--color-accent);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/* ---- Analytics panel ---- */
|
|
150
|
+
.analytics-panel {
|
|
151
|
+
display: flex;
|
|
152
|
+
flex-direction: column;
|
|
153
|
+
gap: var(--space-lg);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
.analytics-panel__section {
|
|
157
|
+
background: var(--color-surface);
|
|
158
|
+
border: 1px solid var(--color-border);
|
|
159
|
+
border-radius: var(--radius-md);
|
|
160
|
+
padding: var(--space-md);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
.analytics-panel__section h2 {
|
|
164
|
+
font-size: 1rem;
|
|
165
|
+
font-weight: 600;
|
|
166
|
+
margin: 0 0 var(--space-md);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.analytics-panel__stats {
|
|
170
|
+
display: flex;
|
|
171
|
+
flex-wrap: wrap;
|
|
172
|
+
gap: var(--space-md);
|
|
173
|
+
margin-bottom: var(--space-md);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.analytics-panel__stat {
|
|
177
|
+
display: flex;
|
|
178
|
+
flex-direction: column;
|
|
179
|
+
gap: var(--space-xs);
|
|
180
|
+
min-width: 8rem;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.analytics-panel__stat-value {
|
|
184
|
+
font-size: 1.5rem;
|
|
185
|
+
font-weight: 700;
|
|
186
|
+
font-family: var(--font-mono);
|
|
187
|
+
color: var(--color-accent);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.analytics-panel__stat-label {
|
|
191
|
+
font-size: 0.8rem;
|
|
192
|
+
color: var(--color-text-dim);
|
|
193
|
+
text-transform: uppercase;
|
|
194
|
+
letter-spacing: 0.05em;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.analytics-panel__table {
|
|
198
|
+
width: 100%;
|
|
199
|
+
border-collapse: collapse;
|
|
200
|
+
font-size: 0.875rem;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.analytics-panel__table th,
|
|
204
|
+
.analytics-panel__table td {
|
|
205
|
+
text-align: left;
|
|
206
|
+
padding: var(--space-xs) var(--space-sm);
|
|
207
|
+
border-bottom: 1px solid var(--color-border);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.analytics-panel__table th {
|
|
211
|
+
color: var(--color-text-dim);
|
|
212
|
+
font-weight: 600;
|
|
213
|
+
font-size: 0.8rem;
|
|
214
|
+
text-transform: uppercase;
|
|
215
|
+
letter-spacing: 0.04em;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
.analytics-panel__empty,
|
|
219
|
+
.analytics-panel__loading {
|
|
220
|
+
color: var(--color-text-dim);
|
|
221
|
+
padding: var(--space-md);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/* ---- Dependency graph ---- */
|
|
225
|
+
.dep-graph {
|
|
226
|
+
background: var(--color-surface);
|
|
227
|
+
border: 1px solid var(--color-border);
|
|
228
|
+
border-radius: var(--radius-md);
|
|
229
|
+
padding: var(--space-md);
|
|
230
|
+
overflow-x: auto;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.dep-graph__container {
|
|
234
|
+
min-height: 12rem;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.dep-graph__loading {
|
|
238
|
+
color: var(--color-text-dim);
|
|
239
|
+
padding: var(--space-md);
|
|
240
|
+
}
|
|
@@ -36,6 +36,7 @@ export function Layout({ title, children, currentView }: LayoutProps) {
|
|
|
36
36
|
<link rel="stylesheet" href="/css/status-colors.css" />
|
|
37
37
|
<link rel="stylesheet" href="/css/command-center.css" />
|
|
38
38
|
<link rel="stylesheet" href="/css/explorer.css" />
|
|
39
|
+
<link rel="stylesheet" href="/css/timeline.css" />
|
|
39
40
|
|
|
40
41
|
{/* Prevent flash of wrong theme */}
|
|
41
42
|
{html`<script>
|
|
@@ -47,6 +48,8 @@ export function Layout({ title, children, currentView }: LayoutProps) {
|
|
|
47
48
|
}
|
|
48
49
|
})();
|
|
49
50
|
</script>`}
|
|
51
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js" defer></script>
|
|
52
|
+
{html`<script>document.addEventListener('DOMContentLoaded', function() { mermaid.initialize({ startOnLoad: false, theme: 'neutral' }); });</script>`}
|
|
50
53
|
</head>
|
|
51
54
|
<body>
|
|
52
55
|
<a href="#main-content" class="skip-link">Skip to content</a>
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
interface ConfigEditorProps {
|
|
2
|
+
config: Record<string, unknown>;
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
function BoolField({ name, label, checked }: { name: string; label: string; checked: boolean }) {
|
|
6
|
+
return (
|
|
7
|
+
<label class="config-field config-field--check">
|
|
8
|
+
<input type="checkbox" name={name} checked={checked || false} />
|
|
9
|
+
{label}
|
|
10
|
+
</label>
|
|
11
|
+
);
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function SelectField({
|
|
15
|
+
name,
|
|
16
|
+
label,
|
|
17
|
+
value,
|
|
18
|
+
options,
|
|
19
|
+
}: {
|
|
20
|
+
name: string;
|
|
21
|
+
label: string;
|
|
22
|
+
value: string;
|
|
23
|
+
options: string[];
|
|
24
|
+
}) {
|
|
25
|
+
return (
|
|
26
|
+
<label class="config-field">
|
|
27
|
+
<span class="config-field__label">{label}</span>
|
|
28
|
+
<select name={name}>
|
|
29
|
+
{options.map((o) => (
|
|
30
|
+
<option value={o} selected={o === value}>
|
|
31
|
+
{o}
|
|
32
|
+
</option>
|
|
33
|
+
))}
|
|
34
|
+
</select>
|
|
35
|
+
</label>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function TextField({
|
|
40
|
+
name,
|
|
41
|
+
label,
|
|
42
|
+
value,
|
|
43
|
+
type,
|
|
44
|
+
readonly,
|
|
45
|
+
}: {
|
|
46
|
+
name: string;
|
|
47
|
+
label: string;
|
|
48
|
+
value: string | number;
|
|
49
|
+
type?: string;
|
|
50
|
+
readonly?: boolean;
|
|
51
|
+
}) {
|
|
52
|
+
return (
|
|
53
|
+
<label class="config-field">
|
|
54
|
+
<span class="config-field__label">{label}</span>
|
|
55
|
+
<input
|
|
56
|
+
type={type || 'text'}
|
|
57
|
+
name={name}
|
|
58
|
+
value={String(value ?? '')}
|
|
59
|
+
readonly={readonly}
|
|
60
|
+
/>
|
|
61
|
+
</label>
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function SectionTitle({ title }: { title: string }) {
|
|
66
|
+
return <h3 class="config-section__title">{title}</h3>;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function ConfigEditor({ config }: ConfigEditorProps) {
|
|
70
|
+
const features = (config.features as Record<string, boolean>) || {};
|
|
71
|
+
const gates = (config.gates as Record<string, boolean>) || {};
|
|
72
|
+
const models = (config.models as Record<string, string>) || {};
|
|
73
|
+
const para = (config.parallelization as Record<string, unknown>) || {};
|
|
74
|
+
const planning = (config.planning as Record<string, unknown>) || {};
|
|
75
|
+
const git = (config.git as Record<string, string>) || {};
|
|
76
|
+
const safety = (config.safety as Record<string, boolean>) || {};
|
|
77
|
+
const localLlm = (config.local_llm as Record<string, unknown>) || {};
|
|
78
|
+
const llmFeatures = (localLlm.features as Record<string, boolean>) || {};
|
|
79
|
+
const llmMetrics = (localLlm.metrics as Record<string, unknown>) || {};
|
|
80
|
+
const llmAdvanced = (localLlm.advanced as Record<string, unknown>) || {};
|
|
81
|
+
|
|
82
|
+
const rawJson = JSON.stringify(config, null, 2);
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div class="config-editor" x-data="{ mode: 'form' }">
|
|
86
|
+
{/* Mode toggle */}
|
|
87
|
+
<div class="config-mode-toggle" role="group" aria-label="Editor mode">
|
|
88
|
+
<button
|
|
89
|
+
type="button"
|
|
90
|
+
class="tab-btn"
|
|
91
|
+
x-on:click="mode = 'form'"
|
|
92
|
+
x-bind:class="{ active: mode === 'form' }"
|
|
93
|
+
>
|
|
94
|
+
Form
|
|
95
|
+
</button>
|
|
96
|
+
<button
|
|
97
|
+
type="button"
|
|
98
|
+
class="tab-btn"
|
|
99
|
+
x-on:click="mode = 'raw'"
|
|
100
|
+
x-bind:class="{ active: mode === 'raw' }"
|
|
101
|
+
>
|
|
102
|
+
Raw JSON
|
|
103
|
+
</button>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
{/* Form mode */}
|
|
107
|
+
<div x-show="mode === 'form'">
|
|
108
|
+
<form
|
|
109
|
+
hx-post="/api/settings/config"
|
|
110
|
+
hx-target="#config-feedback"
|
|
111
|
+
hx-swap="innerHTML"
|
|
112
|
+
hx-encoding="application/x-www-form-urlencoded"
|
|
113
|
+
>
|
|
114
|
+
{/* General section */}
|
|
115
|
+
<div class="config-section">
|
|
116
|
+
<SectionTitle title="General" />
|
|
117
|
+
<SelectField
|
|
118
|
+
name="mode"
|
|
119
|
+
label="Mode"
|
|
120
|
+
value={String(config.mode ?? 'normal')}
|
|
121
|
+
options={['normal', 'autonomous', 'cautious']}
|
|
122
|
+
/>
|
|
123
|
+
<SelectField
|
|
124
|
+
name="depth"
|
|
125
|
+
label="Depth"
|
|
126
|
+
value={String(config.depth ?? 'standard')}
|
|
127
|
+
options={['standard', 'comprehensive', 'light']}
|
|
128
|
+
/>
|
|
129
|
+
<SelectField
|
|
130
|
+
name="context_strategy"
|
|
131
|
+
label="Context Strategy"
|
|
132
|
+
value={String(config.context_strategy ?? 'aggressive')}
|
|
133
|
+
options={['aggressive', 'balanced', 'conservative']}
|
|
134
|
+
/>
|
|
135
|
+
<TextField
|
|
136
|
+
name="version"
|
|
137
|
+
label="Version"
|
|
138
|
+
value={String(config.version ?? '')}
|
|
139
|
+
readonly={true}
|
|
140
|
+
/>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
{/* Features section */}
|
|
144
|
+
<div class="config-section">
|
|
145
|
+
<SectionTitle title="Features" />
|
|
146
|
+
{Object.entries(features).map(([key, val]) => (
|
|
147
|
+
<BoolField name={`features.${key}`} label={key.replace(/_/g, ' ')} checked={val} />
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
|
|
151
|
+
{/* Gates section */}
|
|
152
|
+
<div class="config-section">
|
|
153
|
+
<SectionTitle title="Gates" />
|
|
154
|
+
{Object.entries(gates).map(([key, val]) => (
|
|
155
|
+
<BoolField name={`gates.${key}`} label={key.replace(/_/g, ' ')} checked={val} />
|
|
156
|
+
))}
|
|
157
|
+
</div>
|
|
158
|
+
|
|
159
|
+
{/* Models section */}
|
|
160
|
+
<div class="config-section">
|
|
161
|
+
<SectionTitle title="Models" />
|
|
162
|
+
{Object.entries(models).map(([key, val]) => (
|
|
163
|
+
<SelectField
|
|
164
|
+
name={`models.${key}`}
|
|
165
|
+
label={key}
|
|
166
|
+
value={val}
|
|
167
|
+
options={['haiku', 'sonnet', 'opus', 'inherit']}
|
|
168
|
+
/>
|
|
169
|
+
))}
|
|
170
|
+
</div>
|
|
171
|
+
|
|
172
|
+
{/* Parallelization section */}
|
|
173
|
+
<div class="config-section">
|
|
174
|
+
<SectionTitle title="Parallelization" />
|
|
175
|
+
<BoolField name="parallelization.enabled" label="Enabled" checked={!!para.enabled} />
|
|
176
|
+
<BoolField name="parallelization.plan_level" label="Plan Level" checked={!!para.plan_level} />
|
|
177
|
+
<BoolField name="parallelization.task_level" label="Task Level" checked={!!para.task_level} />
|
|
178
|
+
<BoolField name="parallelization.use_teams" label="Use Teams" checked={!!para.use_teams} />
|
|
179
|
+
<TextField
|
|
180
|
+
name="parallelization.max_concurrent_agents"
|
|
181
|
+
label="Max Concurrent Agents"
|
|
182
|
+
value={Number(para.max_concurrent_agents ?? 3)}
|
|
183
|
+
type="number"
|
|
184
|
+
/>
|
|
185
|
+
<TextField
|
|
186
|
+
name="parallelization.min_plans_for_parallel"
|
|
187
|
+
label="Min Plans for Parallel"
|
|
188
|
+
value={Number(para.min_plans_for_parallel ?? 2)}
|
|
189
|
+
type="number"
|
|
190
|
+
/>
|
|
191
|
+
</div>
|
|
192
|
+
|
|
193
|
+
{/* Planning section */}
|
|
194
|
+
<div class="config-section">
|
|
195
|
+
<SectionTitle title="Planning" />
|
|
196
|
+
<BoolField
|
|
197
|
+
name="planning.commit_docs"
|
|
198
|
+
label="Commit Docs"
|
|
199
|
+
checked={!!planning.commit_docs}
|
|
200
|
+
/>
|
|
201
|
+
<TextField
|
|
202
|
+
name="planning.max_tasks_per_plan"
|
|
203
|
+
label="Max Tasks per Plan"
|
|
204
|
+
value={Number(planning.max_tasks_per_plan ?? 8)}
|
|
205
|
+
type="number"
|
|
206
|
+
/>
|
|
207
|
+
<BoolField
|
|
208
|
+
name="planning.search_gitignored"
|
|
209
|
+
label="Search Gitignored"
|
|
210
|
+
checked={!!planning.search_gitignored}
|
|
211
|
+
/>
|
|
212
|
+
</div>
|
|
213
|
+
|
|
214
|
+
{/* Git section */}
|
|
215
|
+
<div class="config-section">
|
|
216
|
+
<SectionTitle title="Git" />
|
|
217
|
+
<SelectField
|
|
218
|
+
name="git.mode"
|
|
219
|
+
label="Mode"
|
|
220
|
+
value={git.mode ?? 'enabled'}
|
|
221
|
+
options={['enabled', 'disabled']}
|
|
222
|
+
/>
|
|
223
|
+
<SelectField
|
|
224
|
+
name="git.branching"
|
|
225
|
+
label="Branching"
|
|
226
|
+
value={git.branching ?? 'none'}
|
|
227
|
+
options={['none', 'phase', 'milestone']}
|
|
228
|
+
/>
|
|
229
|
+
<TextField
|
|
230
|
+
name="git.commit_format"
|
|
231
|
+
label="Commit Format"
|
|
232
|
+
value={git.commit_format ?? ''}
|
|
233
|
+
/>
|
|
234
|
+
<TextField
|
|
235
|
+
name="git.phase_branch_template"
|
|
236
|
+
label="Phase Branch Template"
|
|
237
|
+
value={git.phase_branch_template ?? ''}
|
|
238
|
+
/>
|
|
239
|
+
<TextField
|
|
240
|
+
name="git.milestone_branch_template"
|
|
241
|
+
label="Milestone Branch Template"
|
|
242
|
+
value={git.milestone_branch_template ?? ''}
|
|
243
|
+
/>
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
{/* Safety section */}
|
|
247
|
+
<div class="config-section">
|
|
248
|
+
<SectionTitle title="Safety" />
|
|
249
|
+
<BoolField
|
|
250
|
+
name="safety.always_confirm_destructive"
|
|
251
|
+
label="Always Confirm Destructive"
|
|
252
|
+
checked={!!safety.always_confirm_destructive}
|
|
253
|
+
/>
|
|
254
|
+
<BoolField
|
|
255
|
+
name="safety.always_confirm_external_services"
|
|
256
|
+
label="Always Confirm External Services"
|
|
257
|
+
checked={!!safety.always_confirm_external_services}
|
|
258
|
+
/>
|
|
259
|
+
</div>
|
|
260
|
+
|
|
261
|
+
{/* Local LLM section */}
|
|
262
|
+
<div class="config-section">
|
|
263
|
+
<SectionTitle title="Local LLM" />
|
|
264
|
+
<BoolField name="local_llm.enabled" label="Enabled" checked={!!localLlm.enabled} />
|
|
265
|
+
<TextField name="local_llm.provider" label="Provider" value={String(localLlm.provider ?? '')} />
|
|
266
|
+
<TextField name="local_llm.endpoint" label="Endpoint" value={String(localLlm.endpoint ?? '')} />
|
|
267
|
+
<TextField name="local_llm.model" label="Model" value={String(localLlm.model ?? '')} />
|
|
268
|
+
<TextField
|
|
269
|
+
name="local_llm.timeout_ms"
|
|
270
|
+
label="Timeout (ms)"
|
|
271
|
+
value={Number(localLlm.timeout_ms ?? 30000)}
|
|
272
|
+
type="number"
|
|
273
|
+
/>
|
|
274
|
+
<TextField
|
|
275
|
+
name="local_llm.max_retries"
|
|
276
|
+
label="Max Retries"
|
|
277
|
+
value={Number(localLlm.max_retries ?? 3)}
|
|
278
|
+
type="number"
|
|
279
|
+
/>
|
|
280
|
+
<SelectField
|
|
281
|
+
name="local_llm.fallback"
|
|
282
|
+
label="Fallback"
|
|
283
|
+
value={String(localLlm.fallback ?? 'frontier')}
|
|
284
|
+
options={['frontier', 'skip', 'error']}
|
|
285
|
+
/>
|
|
286
|
+
<SelectField
|
|
287
|
+
name="local_llm.routing_strategy"
|
|
288
|
+
label="Routing Strategy"
|
|
289
|
+
value={String(localLlm.routing_strategy ?? 'local_first')}
|
|
290
|
+
options={['local_first', 'frontier_first', 'always_local', 'always_frontier']}
|
|
291
|
+
/>
|
|
292
|
+
|
|
293
|
+
{/* LLM Features sub-section */}
|
|
294
|
+
<div class="config-section config-section--nested">
|
|
295
|
+
<SectionTitle title="LLM Features" />
|
|
296
|
+
{Object.entries(llmFeatures).map(([key, val]) => (
|
|
297
|
+
<BoolField
|
|
298
|
+
name={`local_llm.features.${key}`}
|
|
299
|
+
label={key.replace(/_/g, ' ')}
|
|
300
|
+
checked={val}
|
|
301
|
+
/>
|
|
302
|
+
))}
|
|
303
|
+
{Object.keys(llmFeatures).length === 0 && (
|
|
304
|
+
<p class="config-empty">No local LLM features configured.</p>
|
|
305
|
+
)}
|
|
306
|
+
</div>
|
|
307
|
+
|
|
308
|
+
{/* LLM Metrics sub-section */}
|
|
309
|
+
<div class="config-section config-section--nested">
|
|
310
|
+
<SectionTitle title="LLM Metrics" />
|
|
311
|
+
<BoolField
|
|
312
|
+
name="local_llm.metrics.enabled"
|
|
313
|
+
label="Enabled"
|
|
314
|
+
checked={!!llmMetrics.enabled}
|
|
315
|
+
/>
|
|
316
|
+
<BoolField
|
|
317
|
+
name="local_llm.metrics.show_session_summary"
|
|
318
|
+
label="Show Session Summary"
|
|
319
|
+
checked={!!llmMetrics.show_session_summary}
|
|
320
|
+
/>
|
|
321
|
+
<TextField
|
|
322
|
+
name="local_llm.metrics.log_file"
|
|
323
|
+
label="Log File"
|
|
324
|
+
value={String(llmMetrics.log_file ?? '')}
|
|
325
|
+
/>
|
|
326
|
+
<TextField
|
|
327
|
+
name="local_llm.metrics.frontier_token_rate"
|
|
328
|
+
label="Frontier Token Rate"
|
|
329
|
+
value={Number(llmMetrics.frontier_token_rate ?? 0)}
|
|
330
|
+
type="number"
|
|
331
|
+
/>
|
|
332
|
+
</div>
|
|
333
|
+
|
|
334
|
+
{/* LLM Advanced sub-section */}
|
|
335
|
+
<div class="config-section config-section--nested">
|
|
336
|
+
<SectionTitle title="LLM Advanced" />
|
|
337
|
+
<TextField
|
|
338
|
+
name="local_llm.advanced.confidence_threshold"
|
|
339
|
+
label="Confidence Threshold"
|
|
340
|
+
value={Number(llmAdvanced.confidence_threshold ?? 0)}
|
|
341
|
+
type="number"
|
|
342
|
+
/>
|
|
343
|
+
<TextField
|
|
344
|
+
name="local_llm.advanced.max_input_tokens"
|
|
345
|
+
label="Max Input Tokens"
|
|
346
|
+
value={Number(llmAdvanced.max_input_tokens ?? 0)}
|
|
347
|
+
type="number"
|
|
348
|
+
/>
|
|
349
|
+
<TextField
|
|
350
|
+
name="local_llm.advanced.num_ctx"
|
|
351
|
+
label="Num Ctx"
|
|
352
|
+
value={Number(llmAdvanced.num_ctx ?? 0)}
|
|
353
|
+
type="number"
|
|
354
|
+
/>
|
|
355
|
+
<TextField
|
|
356
|
+
name="local_llm.advanced.disable_after_failures"
|
|
357
|
+
label="Disable After Failures"
|
|
358
|
+
value={Number(llmAdvanced.disable_after_failures ?? 0)}
|
|
359
|
+
type="number"
|
|
360
|
+
/>
|
|
361
|
+
<TextField
|
|
362
|
+
name="local_llm.advanced.keep_alive"
|
|
363
|
+
label="Keep Alive"
|
|
364
|
+
value={String(llmAdvanced.keep_alive ?? '')}
|
|
365
|
+
/>
|
|
366
|
+
<BoolField
|
|
367
|
+
name="local_llm.advanced.shadow_mode"
|
|
368
|
+
label="Shadow Mode"
|
|
369
|
+
checked={!!llmAdvanced.shadow_mode}
|
|
370
|
+
/>
|
|
371
|
+
</div>
|
|
372
|
+
</div>
|
|
373
|
+
|
|
374
|
+
<div class="config-actions">
|
|
375
|
+
<button type="submit" class="btn btn--primary">Save Config</button>
|
|
376
|
+
</div>
|
|
377
|
+
</form>
|
|
378
|
+
<div id="config-feedback" class="config-feedback" aria-live="polite"></div>
|
|
379
|
+
</div>
|
|
380
|
+
|
|
381
|
+
{/* Raw JSON mode */}
|
|
382
|
+
<div x-show="mode === 'raw'" x-cloak>
|
|
383
|
+
<form
|
|
384
|
+
hx-post="/api/settings/config"
|
|
385
|
+
hx-target="#config-feedback-raw"
|
|
386
|
+
hx-swap="innerHTML"
|
|
387
|
+
>
|
|
388
|
+
<textarea name="rawJson" rows={30} class="config-raw-json">
|
|
389
|
+
{rawJson}
|
|
390
|
+
</textarea>
|
|
391
|
+
<div class="config-actions">
|
|
392
|
+
<button type="submit" class="btn btn--primary">Save JSON</button>
|
|
393
|
+
</div>
|
|
394
|
+
</form>
|
|
395
|
+
<div id="config-feedback-raw" class="config-feedback" aria-live="polite"></div>
|
|
396
|
+
</div>
|
|
397
|
+
</div>
|
|
398
|
+
);
|
|
399
|
+
}
|