@thebackstoryis/engineering-with-ai 0.2.9 → 0.3.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/Docs/README.md +2 -0
- package/Docs/autonomous-intent-delivery.md +37 -0
- package/Docs/guide-catalogue.md +2 -0
- package/Docs/quality/persona-driven-test-scenarios.md +6 -0
- package/README.md +17 -0
- package/config/autonomy-worker.schema.json +70 -0
- package/config/autonomy.schema.json +25 -0
- package/package.json +3 -3
- package/public/app.js +7 -0
- package/public/autonomy.js +470 -0
- package/public/index.html +26 -0
- package/public/styles.css +64 -0
- package/skills-src/ewai-autonomy/SKILL.md +18 -0
- package/skills-src/ewai-autonomy/agents/openai.yaml +4 -0
- package/skills-src/ewai-deliver/SKILL.md +2 -0
- package/skills-src/ewai-test-scenarios/references/scenario-contract.md +6 -0
- package/src/autonomy-phase-contracts.mjs +88 -0
- package/src/autonomy-worker.mjs +15 -0
- package/src/autonomy.mjs +199 -0
- package/src/checkin.mjs +30 -6
- package/src/cli.mjs +67 -0
- package/src/delivery-gates.mjs +8 -1
- package/src/delivery.mjs +74 -3
- package/src/design-system-application.mjs +28 -0
- package/src/runtime/afk-conductor.mjs +191 -71
- package/src/runtime/autonomy-operations.mjs +250 -0
- package/src/runtime/autonomy-supervisor.mjs +426 -0
- package/src/runtime/autonomy-workers.mjs +333 -0
- package/src/runtime/autonomy-workspace.mjs +310 -0
- package/src/runtime/dashboard-actions.mjs +69 -0
- package/src/runtime/dashboard-server.mjs +25 -0
- package/src/runtime/database.mjs +25 -6
- package/src/runtime/intent-ownership.mjs +272 -0
- package/src/runtime/mcp-server.mjs +38 -0
- package/src/runtime/provider-adapters.mjs +365 -7
- package/src/runtime/work.mjs +21 -5
- package/src/test-scenarios.mjs +173 -20
|
@@ -0,0 +1,470 @@
|
|
|
1
|
+
// Browser projections and controls for the server-owned autonomy contract.
|
|
2
|
+
// No project root, provider command or credential is accepted from this module.
|
|
3
|
+
const $ = id => document.getElementById(id);
|
|
4
|
+
const view = { status: null, preview: null, draftPreview: null, draftIds: new Set(), poolSignature: '',
|
|
5
|
+
draftTouched: false, draftHydrated: false, loading: false, busy: false, dispatching: false,
|
|
6
|
+
resuming: new Set(), error: '', notice: '', requestRevision: 0, control: null, answer: null, runIntentId: '' };
|
|
7
|
+
let host = null;
|
|
8
|
+
|
|
9
|
+
function node(tag, content = '', className = '') {
|
|
10
|
+
const element = document.createElement(tag);
|
|
11
|
+
if (className) element.className = className;
|
|
12
|
+
element.textContent = String(content ?? '');
|
|
13
|
+
return element;
|
|
14
|
+
}
|
|
15
|
+
function set(id, content) { $(id).textContent = String(content ?? ''); }
|
|
16
|
+
function replace(id, ...children) { $(id).replaceChildren(...children); }
|
|
17
|
+
function replacePreservingFocus(id, ...children) {
|
|
18
|
+
const container = $(id), focused = container.contains(document.activeElement) ? document.activeElement.dataset.autonomyFocus : '';
|
|
19
|
+
container.replaceChildren(...children);
|
|
20
|
+
if (!focused) return;
|
|
21
|
+
const replacement = [...container.querySelectorAll('[data-autonomy-focus]')].find(element => element.dataset.autonomyFocus === focused && !element.disabled);
|
|
22
|
+
if (replacement) replacement.focus();
|
|
23
|
+
else (id === 'autonomyLiveRuns' ? $('autonomyLiveTitle') : $('autonomyCompanionStatus')).focus();
|
|
24
|
+
}
|
|
25
|
+
function staleDecision(code = 'autonomy-preview-stale') { return { status: 409, body: { code } }; }
|
|
26
|
+
function itemFor(intentId) { return host.getItems().find(item => item.intentId === intentId); }
|
|
27
|
+
function titleFor(intentId) { return itemFor(intentId)?.title || intentId; }
|
|
28
|
+
function humanReason(code) {
|
|
29
|
+
return ({ 'grant-revoked': 'Grant revoked', 'outside-approved-pool': 'Outside your exact pool',
|
|
30
|
+
'autonomy-off': 'Autonomy off', 'build-approval-required': 'Build approval needed',
|
|
31
|
+
'prototype-selection-required': 'Prototype selection needed', 'manual-qa-required': 'Manual QA needed',
|
|
32
|
+
'repository-ownership-conflict': 'Repository is in use', 'afk-preflight-required': 'Build preflight needed',
|
|
33
|
+
'action-not-approved': 'Action is outside this grant', 'grant-policy-changed': 'Grant needs a fresh review',
|
|
34
|
+
'grant-expired': 'Grant expired', 'autonomy-provider-unavailable': 'Provider unavailable',
|
|
35
|
+
'autonomy-execution-unknown': 'Execution outcome unknown' })[code] || String(code ?? 'A prerequisite needs review').replaceAll('-', ' ');
|
|
36
|
+
}
|
|
37
|
+
function safeError(error) {
|
|
38
|
+
const code = error?.body?.code;
|
|
39
|
+
if (code === 'autonomy-preview-stale' || code === 'autonomy-run-stale' || code === 'autonomy-grant-stale'
|
|
40
|
+
|| code === 'autonomy-policy-stale') return 'The evidence changed. Refresh and review the current state before trying again.';
|
|
41
|
+
if (code === 'autonomy-repository-dirty') return 'The repository has uncommitted work. Resolve it before dispatch.';
|
|
42
|
+
if (code === 'autonomy-canonical-decision-required') return 'A named human decision is required before this run can continue.';
|
|
43
|
+
return /^autonomy-[a-z0-9-]+$/.test(code ?? '') ? `EWAI stopped at ${humanReason(code)}. Refresh and inspect the recorded prerequisite.`
|
|
44
|
+
: 'Autonomy is unavailable. Refresh the dashboard and inspect the project checks.';
|
|
45
|
+
}
|
|
46
|
+
const mutation = (action, input) => host.api(`/api/autonomy/${action}`, { method: 'POST', body: JSON.stringify(input) });
|
|
47
|
+
function badge() {
|
|
48
|
+
if (!view.status) return 'Autonomy unavailable';
|
|
49
|
+
if (view.status.status === 'revoked') return 'Grant revoked';
|
|
50
|
+
if (view.status.status === 'expired') return 'Grant expired';
|
|
51
|
+
return view.status.mode === 'delegated' ? 'Delegated within your grant' : 'Autonomy off';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function renderPool() {
|
|
55
|
+
const items = host.getItems().filter(item => item.intentId).sort((a, b) => a.intentId.localeCompare(b.intentId));
|
|
56
|
+
const signature = JSON.stringify(items.map(item => [item.intentId, item.title]));
|
|
57
|
+
if (signature === view.poolSignature) return;
|
|
58
|
+
view.poolSignature = signature;
|
|
59
|
+
const choices = items.map(item => {
|
|
60
|
+
const choice = node('button', '', 'autonomy-pool-choice');
|
|
61
|
+
choice.type = 'button'; choice.setAttribute('role', 'checkbox'); choice.setAttribute('aria-checked', String(view.draftIds.has(item.intentId)));
|
|
62
|
+
choice.value = item.intentId;
|
|
63
|
+
choice.addEventListener('click', () => {
|
|
64
|
+
const checked = choice.getAttribute('aria-checked') !== 'true';
|
|
65
|
+
choice.setAttribute('aria-checked', String(checked));
|
|
66
|
+
if (checked) view.draftIds.add(item.intentId); else view.draftIds.delete(item.intentId);
|
|
67
|
+
view.draftTouched = true;
|
|
68
|
+
view.draftPreview = null; renderAutonomyConfiguration();
|
|
69
|
+
});
|
|
70
|
+
const mark = node('span', '✓', 'autonomy-pool-mark'); mark.setAttribute('aria-hidden', 'true');
|
|
71
|
+
choice.append(mark, node('span', item.title || item.intentId), node('small', item.intentId));
|
|
72
|
+
return choice;
|
|
73
|
+
});
|
|
74
|
+
replace('autonomyPool', ...(choices.length ? choices : [node('p', 'No project intents are available for an exact pool.')]));
|
|
75
|
+
}
|
|
76
|
+
function selectedProposal() {
|
|
77
|
+
const intents = [...view.draftIds].sort();
|
|
78
|
+
const actions = [...document.querySelectorAll('[name="autonomyAction"][aria-checked="true"]')].map(input => input.value);
|
|
79
|
+
const provider = $('autonomyProvider').value;
|
|
80
|
+
const hours = Number($('autonomyExpiryHours').value), runtimeMinutes = Number($('autonomyRuntimeMinutes').value);
|
|
81
|
+
const operationSeconds = Number($('autonomyOperationSeconds').value), attempts = Number($('autonomyAttempts').value);
|
|
82
|
+
if (!intents.length || !actions.length || !Number.isSafeInteger(hours) || hours < 1 || hours > 24
|
|
83
|
+
|| !Number.isSafeInteger(runtimeMinutes) || runtimeMinutes < 1 || runtimeMinutes > 1440
|
|
84
|
+
|| !Number.isSafeInteger(operationSeconds) || operationSeconds < 1 || operationSeconds > 3600
|
|
85
|
+
|| operationSeconds > runtimeMinutes * 60 || !Number.isSafeInteger(attempts) || attempts < 1 || attempts > 100) {
|
|
86
|
+
throw new Error('Choose at least one exact intent and action, then enter valid expiry and limits.');
|
|
87
|
+
}
|
|
88
|
+
return { intentIds: intents, actions, providers: [provider], expiresAt: new Date(Date.now() + hours * 3600000).toISOString(),
|
|
89
|
+
limits: { maxConcurrentIntents: 1, maxRuntimeMs: runtimeMinutes * 60000, maxOperationMs: operationSeconds * 1000, maxAttempts: attempts } };
|
|
90
|
+
}
|
|
91
|
+
function grantSummary(scope) {
|
|
92
|
+
if (!scope) return 'No approved grant. Select an exact pool to preview it.';
|
|
93
|
+
return `Exact pool: ${scope.intentIds.join(', ')}. Actions: ${scope.actions.join(', ')}. Provider: ${scope.providers.join(', ')}. One intent at a time; ${Math.round(scope.limits.maxRuntimeMs / 60000)} minute run, ${Math.round(scope.limits.maxOperationMs / 1000)} second operation, ${scope.limits.maxAttempts} attempts. Expires ${new Date(scope.expiresAt).toLocaleString()}.`;
|
|
94
|
+
}
|
|
95
|
+
function draftIsApprovable() {
|
|
96
|
+
const preview = view.draftPreview;
|
|
97
|
+
return Boolean(!view.error && preview?.proposal?.intentIds?.length);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function renderAutonomyConfiguration() {
|
|
101
|
+
if (!host) return;
|
|
102
|
+
renderPool();
|
|
103
|
+
set('autonomyMode', badge());
|
|
104
|
+
$('autonomyMode').className = `autonomy-mode ${view.status?.status === 'current' ? 'delegated' : view.status?.status === 'revoked' ? 'revoked' : ''}`;
|
|
105
|
+
$('autonomyReviewButton').disabled = view.busy || !draftIsApprovable();
|
|
106
|
+
$('autonomyRevokeButton').disabled = view.busy || view.status?.status !== 'current';
|
|
107
|
+
$('autonomyPreviewButton').disabled = view.busy;
|
|
108
|
+
set('autonomyConfigurationStatus', view.error || view.notice || (view.loading ? 'Checking current authority and work…'
|
|
109
|
+
: view.status?.status === 'current' ? 'Grant active. Project evidence and all human checkpoints are rechecked before dispatch.'
|
|
110
|
+
: view.status?.status === 'revoked' ? 'Grant revoked. Pending work remains visible and no new action can start.'
|
|
111
|
+
: 'Autonomy is off until you approve a named grant.'));
|
|
112
|
+
const saved = view.status?.grant?.scope ? `Current approved grant: ${grantSummary(view.status.grant.scope)}` : '';
|
|
113
|
+
const summary = view.draftPreview
|
|
114
|
+
? `${saved ? `${saved} ` : ''}New grant draft preview: ${grantSummary(view.draftPreview.proposal)} Preview only: ${view.draftPreview.executable.length} can progress; ${view.draftPreview.humanDecisions.length} need people; ${view.draftPreview.blocked.length} are blocked.`
|
|
115
|
+
: saved || grantSummary(null);
|
|
116
|
+
set('autonomyProposalSummary', summary);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function candidateCard(candidate, kind, allowRun = false) {
|
|
120
|
+
const card = node('article', '', `autonomy-candidate ${kind}`);
|
|
121
|
+
card.append(node('h5', titleFor(candidate.intentId)), node('small', candidate.intentId));
|
|
122
|
+
const action = candidate.action ? humanReason(candidate.action) : 'No executable action';
|
|
123
|
+
card.append(node('p', `Next: ${action}. Phase: ${candidate.phase || 'unrecorded'}.`));
|
|
124
|
+
if (candidate.reasons?.length) card.append(node('p', candidate.reasons.map(reason => humanReason(reason.code)).join(' · '), 'autonomy-reasons'));
|
|
125
|
+
const buttons = node('div', '', 'autonomy-card-actions');
|
|
126
|
+
if (kind === 'executable') {
|
|
127
|
+
const run = node('button', 'Run next action', 'primary-action'); run.type = 'button';
|
|
128
|
+
run.dataset.autonomyFocus = `dispatch:${candidate.intentId}`;
|
|
129
|
+
run.disabled = !allowRun; run.addEventListener('click', () => openDispatch(candidate.intentId, run)); buttons.append(run);
|
|
130
|
+
if (!allowRun && view.status?.status === 'current' && !view.busy && !view.dispatching) card.append(node('p', 'Ordered after an earlier eligible action.', 'autonomy-reasons'));
|
|
131
|
+
}
|
|
132
|
+
const inspect = node('button', 'Inspect intent', 'secondary-action'); inspect.type = 'button';
|
|
133
|
+
inspect.dataset.autonomyFocus = `inspect:${candidate.intentId}`;
|
|
134
|
+
inspect.addEventListener('click', () => { const item = itemFor(candidate.intentId); if (item) host.openIntent(item.id); });
|
|
135
|
+
buttons.append(inspect); card.append(buttons); return card;
|
|
136
|
+
}
|
|
137
|
+
function emptyCard(message, withDisabledRun = false) {
|
|
138
|
+
const card = node('div', '', 'autonomy-empty'); card.append(node('p', message));
|
|
139
|
+
if (withDisabledRun) { const button = node('button', 'Run next action', 'primary-action'); button.type = 'button'; button.disabled = true; card.append(button); }
|
|
140
|
+
return card;
|
|
141
|
+
}
|
|
142
|
+
export function renderAutonomyDecisions() {
|
|
143
|
+
if (!host) return;
|
|
144
|
+
const preview = view.draftPreview || view.preview;
|
|
145
|
+
if (!preview || view.error) {
|
|
146
|
+
set('autonomyCompanionStatus', view.error || 'Checking governed work…');
|
|
147
|
+
replacePreservingFocus('autonomyCanProgress', emptyCard('No trusted eligibility projection is available.', true));
|
|
148
|
+
replacePreservingFocus('autonomyNeedsHuman', emptyCard('Human checkpoints will appear here.'));
|
|
149
|
+
replacePreservingFocus('autonomyBlocked', emptyCard('Refresh to inspect exclusions.'));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
const granted = view.status?.status === 'current' && !view.draftPreview;
|
|
153
|
+
set('autonomyCompanionStatus', granted ? 'Bounded by the current named grant. Each action is rechecked before dispatch.'
|
|
154
|
+
: view.status?.status === 'revoked' ? 'Grant revoked. Pending work is retained below; dispatch is disabled.'
|
|
155
|
+
: 'Preview only. No provider or project action starts from this list.');
|
|
156
|
+
replacePreservingFocus('autonomyCanProgress', ...(preview.executable.length
|
|
157
|
+
? preview.executable.map((candidate, index) => candidateCard(candidate, 'executable', granted && !view.busy && !view.dispatching && index === 0))
|
|
158
|
+
: [emptyCard('No work can progress under the current authority.', true)]));
|
|
159
|
+
replacePreservingFocus('autonomyNeedsHuman', ...(preview.humanDecisions.length
|
|
160
|
+
? preview.humanDecisions.map(candidate => candidateCard(candidate, 'human'))
|
|
161
|
+
: [emptyCard('No human checkpoint is waiting in this pool.')]));
|
|
162
|
+
replacePreservingFocus('autonomyBlocked', ...(preview.blocked.length
|
|
163
|
+
? preview.blocked.map(candidate => candidateCard(candidate, 'blocked'))
|
|
164
|
+
: [emptyCard('No additional exclusions in this projection.')]));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function runExplanation(run) {
|
|
168
|
+
if (run.status === 'cancel-requested' || run.cancellation?.status === 'requested') return 'Cancellation requested. Process termination is not yet confirmed; do not retry the action.';
|
|
169
|
+
if (run.status === 'cancelled' && run.cancellation?.status === 'confirmed') return 'Process stop confirmed. Captured work remains available for review.';
|
|
170
|
+
if (run.status === 'recovery-required' || run.cancellation?.status === 'unknown') return 'Outcome uncertain. Reconcile the run and canonical evidence before any retry.';
|
|
171
|
+
if (run.status === 'awaiting-human') return 'Stopped at a human checkpoint. A recorded answer does not approve the gate.';
|
|
172
|
+
if (run.status === 'paused') return 'Paused before another dispatch. Resume rechecks authority, evidence and limits.';
|
|
173
|
+
if (run.status === 'completed') return 'Bounded action completed. Human approvals remain separate.';
|
|
174
|
+
return 'The current action is bounded by the approved grant and recorded limits.';
|
|
175
|
+
}
|
|
176
|
+
function questionContext(question) {
|
|
177
|
+
return `${titleFor(question.intentId)} · ${question.phase} · owner ${question.owner} · ${question.codes.map(humanReason).join(' · ') || 'Named human decision'}`;
|
|
178
|
+
}
|
|
179
|
+
function renderAutonomyLive() {
|
|
180
|
+
if (!host) return;
|
|
181
|
+
set('autonomyLiveStatus', view.error || view.notice);
|
|
182
|
+
if (!view.status) return replacePreservingFocus('autonomyLiveRuns', emptyCard(view.error || 'Run status is unavailable.'));
|
|
183
|
+
const runs = view.status.runs || [];
|
|
184
|
+
if (!runs.length) return replacePreservingFocus('autonomyLiveRuns', emptyCard(view.error || (view.dispatching
|
|
185
|
+
? 'Dispatch request pending. Run controls appear when the server records a run.' : 'No delegated run has started in this project.')));
|
|
186
|
+
const grantUsage = new Map();
|
|
187
|
+
for (const run of runs) {
|
|
188
|
+
const used = grantUsage.get(run.grantDigest) || { attempts: 0, elapsedMs: 0 };
|
|
189
|
+
used.attempts += run.counters.providerAttempts;
|
|
190
|
+
used.elapsedMs += run.counters.elapsedMs;
|
|
191
|
+
grantUsage.set(run.grantDigest, used);
|
|
192
|
+
}
|
|
193
|
+
const cards = runs.slice().reverse().map(run => {
|
|
194
|
+
const card = node('article', '', 'autonomy-run');
|
|
195
|
+
card.append(node('h4', `Run ${run.id.slice(0, 8)} · ${run.status}`), node('p', runExplanation(run)));
|
|
196
|
+
if (run.code) card.append(node('p', humanReason(run.code), 'autonomy-reasons'));
|
|
197
|
+
const limits = run.grantDigest === view.status.grant?.digest ? view.status.grant.scope.limits : null;
|
|
198
|
+
const used = grantUsage.get(run.grantDigest);
|
|
199
|
+
if (limits) card.append(node('small', `Attempts ${used.attempts}/${limits.maxAttempts}; runtime ${Math.max(0, Math.ceil((limits.maxRuntimeMs - used.elapsedMs) / 60000))} minutes remaining. Recorded grant-wide usage may be incomplete.`));
|
|
200
|
+
else card.append(node('small', 'Historical grant. Current limits do not describe this run.'));
|
|
201
|
+
card.append(node('small', ` Owner process ${run.lifetime?.ownerProcessAlive ? 'reported alive' : 'not alive or unconfirmed'}.`));
|
|
202
|
+
const actions = node('div', '', 'autonomy-card-actions');
|
|
203
|
+
for (const action of ['pause', 'resume', 'cancel', 'recover']) {
|
|
204
|
+
const available = action === 'pause' ? ['starting', 'running', 'awaiting-human'].includes(run.status)
|
|
205
|
+
: action === 'resume' ? run.status === 'paused' : action === 'cancel' ? !['completed', 'cancelled', 'cancel-requested', 'recovery-required', 'revoked', 'expired', 'blocked'].includes(run.status)
|
|
206
|
+
: run.status === 'recovery-required';
|
|
207
|
+
if (!available) continue;
|
|
208
|
+
const label = action === 'cancel' ? 'Request cancellation' : action === 'recover' ? 'Review recovery' : `${action[0].toUpperCase()}${action.slice(1)} run`;
|
|
209
|
+
const button = node('button', label, action === 'cancel' ? 'secondary-action danger' : 'secondary-action'); button.type = 'button';
|
|
210
|
+
button.disabled = action === 'resume' && view.resuming.has(run.id);
|
|
211
|
+
button.dataset.autonomyFocus = `control:${run.id}:${action}`;
|
|
212
|
+
button.addEventListener('click', () => openControl(run, action, button)); actions.append(button);
|
|
213
|
+
}
|
|
214
|
+
for (const question of run.questions || []) {
|
|
215
|
+
card.append(node('p', questionContext(question), 'autonomy-question-context'));
|
|
216
|
+
const button = node('button', `Record human answer for ${titleFor(question.intentId)}`, 'secondary-action'); button.type = 'button';
|
|
217
|
+
button.dataset.autonomyFocus = `answer:${run.id}:${question.id}`;
|
|
218
|
+
button.addEventListener('click', () => openAnswer(run, question, button)); actions.append(button);
|
|
219
|
+
}
|
|
220
|
+
card.append(actions); return card;
|
|
221
|
+
});
|
|
222
|
+
replacePreservingFocus('autonomyLiveRuns', ...(view.error ? [node('p', view.error, 'autonomy-reasons')] : []), ...cards);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
export function renderAutonomyIntentDetail(item) {
|
|
226
|
+
if (!host || !item?.intentId) return;
|
|
227
|
+
const panel = node('section', '', 'autonomy-intent-detail');
|
|
228
|
+
panel.append(node('h3', 'Delegated delivery'));
|
|
229
|
+
const candidate = ['executable', 'humanDecisions', 'blocked'].flatMap(key => view.preview?.[key] || []).find(value => value.intentId === item.intentId);
|
|
230
|
+
const inPool = view.status?.grant?.scope?.intentIds.includes(item.intentId);
|
|
231
|
+
panel.append(node('p', view.error || (!view.status ? 'Authority status is unavailable.' : inPool ? 'This intent is in the exact recorded grant.' : 'This intent is outside the recorded grant.')));
|
|
232
|
+
if (candidate) panel.append(node('p', candidate.reasons?.length ? candidate.reasons.map(reason => humanReason(reason.code)).join(' · ') : `Next permitted action: ${humanReason(candidate.action)}.`));
|
|
233
|
+
$('drawerContent').append(panel);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
function openDialog(id, opener, focusId) {
|
|
237
|
+
const dialog = $(id); dialog._autonomyOpener = opener;
|
|
238
|
+
dialog._autonomyOpenerId = opener?.id || '';
|
|
239
|
+
dialog._autonomyOpenerKey = opener?.dataset.autonomyFocus || '';
|
|
240
|
+
dialog.querySelector('[data-autonomy-error]').textContent = '';
|
|
241
|
+
dialog.showModal(); (focusId ? $(focusId) : dialog.querySelector('button[type="submit"]'))?.focus();
|
|
242
|
+
}
|
|
243
|
+
function openDispatch(intentId, opener) {
|
|
244
|
+
if (view.status?.status !== 'current' || view.preview?.executable[0]?.intentId !== intentId) return;
|
|
245
|
+
view.runIntentId = intentId;
|
|
246
|
+
view.dispatch = { intentId, grantDigest: view.status.grant.digest, provider: view.status.grant.scope.providers[0] };
|
|
247
|
+
set('autonomyDispatchSummary', `${titleFor(intentId)} is next by recorded priority. ${grantSummary(view.status.grant.scope)}`);
|
|
248
|
+
openDialog('autonomyDispatchDialog', opener, 'autonomyDispatchMode');
|
|
249
|
+
}
|
|
250
|
+
function openControl(run, action, opener) {
|
|
251
|
+
if (action === 'resume' && view.resuming.has(run.id)) return;
|
|
252
|
+
view.control = { runId: run.id, revision: run.revision, action };
|
|
253
|
+
set('autonomyControlTitle', `${action[0].toUpperCase()}${action.slice(1)} delegated run`);
|
|
254
|
+
set('autonomyControlSummary', `Run ${run.id.slice(0, 8)} at revision ${run.revision}.`);
|
|
255
|
+
set('autonomyControlBoundary', action === 'cancel' ? 'This requests a stop. The process may still be alive until termination is confirmed.'
|
|
256
|
+
: action === 'recover' ? 'Recovery checks what actually happened; it never retries an uncertain operation blindly.'
|
|
257
|
+
: 'EWAI checks current authority, ownership, evidence and limits before changing this run.');
|
|
258
|
+
set('autonomyControlConfirm', action === 'cancel' ? 'Request cancellation' : `Confirm ${action}`);
|
|
259
|
+
openDialog('autonomyControlDialog', opener);
|
|
260
|
+
}
|
|
261
|
+
function openAnswer(run, question, opener) {
|
|
262
|
+
view.answer = { runId: run.id, questionId: question.id, revision: run.revision };
|
|
263
|
+
set('autonomyAnswerSummary', questionContext(question));
|
|
264
|
+
openDialog('autonomyAnswerDialog', opener, 'autonomyAnswerName');
|
|
265
|
+
}
|
|
266
|
+
function busy(value) {
|
|
267
|
+
view.busy = value;
|
|
268
|
+
document.querySelectorAll('.autonomy-dialog button[type="submit"]').forEach(button => { button.disabled = value; });
|
|
269
|
+
renderAutonomyConfiguration();
|
|
270
|
+
if (!document.querySelector('.autonomy-dialog[open]')) renderAutonomyDecisions();
|
|
271
|
+
}
|
|
272
|
+
async function perform(operation) {
|
|
273
|
+
if (view.busy) return;
|
|
274
|
+
view.error = '';
|
|
275
|
+
busy(true);
|
|
276
|
+
try { await operation(); }
|
|
277
|
+
catch (error) {
|
|
278
|
+
view.notice = error?.message === 'Choose at least one exact intent and action, then enter valid expiry and limits.' ? error.message : safeError(error);
|
|
279
|
+
const dialog = document.querySelector('.autonomy-dialog[open]');
|
|
280
|
+
if (dialog) dialog.querySelector('[data-autonomy-error]').textContent = view.notice;
|
|
281
|
+
if (error?.status === 409) await loadAutonomyState();
|
|
282
|
+
}
|
|
283
|
+
finally { busy(false); renderAutonomyConfiguration(); renderAutonomyDecisions(); renderAutonomyLive(); }
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
export async function submitAutonomyControl(action, revision, runId) {
|
|
287
|
+
const result = await mutation('control', { action, runId, expectedRevision: revision, confirmed: true });
|
|
288
|
+
view.notice = result.cancellation?.status === 'unknown' || result.cancellation?.status === 'requested'
|
|
289
|
+
? 'Cancellation requested; process termination is not yet confirmed.'
|
|
290
|
+
: result.cancellation?.status === 'confirmed' ? 'Process stop confirmed. Captured work is preserved.'
|
|
291
|
+
: `Run ${result.status}. Current evidence has been refreshed.`;
|
|
292
|
+
await loadAutonomyState();
|
|
293
|
+
return result;
|
|
294
|
+
}
|
|
295
|
+
export async function loadAutonomyState() {
|
|
296
|
+
if (!host) return;
|
|
297
|
+
const revision = ++view.requestRevision;
|
|
298
|
+
view.loading = true; renderAutonomyConfiguration();
|
|
299
|
+
try {
|
|
300
|
+
const status = await host.api('/api/autonomy');
|
|
301
|
+
if (revision !== view.requestRevision) return;
|
|
302
|
+
view.status = status;
|
|
303
|
+
if (!view.draftHydrated && !view.draftTouched && status.grant?.scope?.intentIds) {
|
|
304
|
+
const scope = status.grant.scope;
|
|
305
|
+
view.draftIds = new Set(scope.intentIds);
|
|
306
|
+
view.poolSignature = '';
|
|
307
|
+
for (const button of document.querySelectorAll('[name="autonomyAction"]')) {
|
|
308
|
+
button.setAttribute('aria-checked', String(scope.actions.includes(button.value)));
|
|
309
|
+
}
|
|
310
|
+
if ([...$('autonomyProvider').options].some(option => option.value === scope.providers[0])) {
|
|
311
|
+
$('autonomyProvider').value = scope.providers[0];
|
|
312
|
+
}
|
|
313
|
+
$('autonomyRuntimeMinutes').value = String(Math.round(scope.limits.maxRuntimeMs / 60000));
|
|
314
|
+
$('autonomyOperationSeconds').value = String(Math.round(scope.limits.maxOperationMs / 1000));
|
|
315
|
+
$('autonomyAttempts').value = String(scope.limits.maxAttempts);
|
|
316
|
+
const hours = Math.ceil((Date.parse(scope.expiresAt) - Date.now()) / 3600000);
|
|
317
|
+
$('autonomyExpiryHours').value = String(Math.max(1, Math.min(24, hours)));
|
|
318
|
+
}
|
|
319
|
+
view.draftHydrated = true;
|
|
320
|
+
try {
|
|
321
|
+
const preview = await mutation('preview', {});
|
|
322
|
+
if (revision !== view.requestRevision) return;
|
|
323
|
+
view.preview = preview; view.error = '';
|
|
324
|
+
} catch (error) {
|
|
325
|
+
if (revision !== view.requestRevision) return;
|
|
326
|
+
view.preview = null; view.error = `Eligibility preview unavailable. ${safeError(error)}`;
|
|
327
|
+
}
|
|
328
|
+
} catch (error) {
|
|
329
|
+
if (revision !== view.requestRevision) return;
|
|
330
|
+
view.status = null; view.preview = null; view.error = safeError(error);
|
|
331
|
+
} finally {
|
|
332
|
+
if (revision === view.requestRevision) {
|
|
333
|
+
view.loading = false; renderAutonomyConfiguration();
|
|
334
|
+
if (!document.querySelector('.autonomy-dialog[open]')) { renderAutonomyDecisions(); renderAutonomyLive(); }
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
export function configureAutonomyDashboard(options) {
|
|
340
|
+
host = options;
|
|
341
|
+
$('autonomyPreviewButton').addEventListener('click', () => perform(async () => {
|
|
342
|
+
const proposal = selectedProposal();
|
|
343
|
+
view.draftPreview = await mutation('preview', { proposal, record: false, confirmed: false });
|
|
344
|
+
view.notice = 'Preview only. No grant or provider has started.';
|
|
345
|
+
renderAutonomyConfiguration(); renderAutonomyDecisions();
|
|
346
|
+
}));
|
|
347
|
+
for (const input of document.querySelectorAll('.autonomy-configuration input, .autonomy-configuration select')) {
|
|
348
|
+
input.addEventListener('change', () => { view.draftTouched = true; view.draftPreview = null; renderAutonomyConfiguration(); renderAutonomyDecisions(); });
|
|
349
|
+
}
|
|
350
|
+
for (const button of document.querySelectorAll('[name="autonomyAction"]')) button.addEventListener('click', () => {
|
|
351
|
+
button.setAttribute('aria-checked', String(button.getAttribute('aria-checked') !== 'true'));
|
|
352
|
+
view.draftTouched = true; view.draftPreview = null; renderAutonomyConfiguration(); renderAutonomyDecisions();
|
|
353
|
+
});
|
|
354
|
+
$('autonomyReviewButton').addEventListener('click', event => {
|
|
355
|
+
if (!draftIsApprovable()) return;
|
|
356
|
+
set('autonomyApprovalSummary', grantSummary(view.draftPreview.proposal));
|
|
357
|
+
$('autonomyApprovalDialog')._reviewedPreview = view.draftPreview;
|
|
358
|
+
openDialog('autonomyApprovalDialog', event.currentTarget, 'autonomyApproverName');
|
|
359
|
+
});
|
|
360
|
+
$('autonomyApprovalForm').addEventListener('submit', event => {
|
|
361
|
+
event.preventDefault(); const name = $('autonomyApproverName').value.trim();
|
|
362
|
+
if (!name || !draftIsApprovable()) return;
|
|
363
|
+
perform(async () => {
|
|
364
|
+
const reviewed = $('autonomyApprovalDialog')._reviewedPreview;
|
|
365
|
+
const recorded = await mutation('preview', { proposal: reviewed.proposal, record: true, confirmed: true });
|
|
366
|
+
if (recorded.digest !== reviewed.digest) { view.draftPreview = null; throw staleDecision(); }
|
|
367
|
+
await mutation('approve', { expectedDigest: recorded.digest, approvedBy: name, confirmed: true });
|
|
368
|
+
$('autonomyApprovalDialog').close(); view.draftPreview = null;
|
|
369
|
+
view.notice = 'Grant approved. Current project evidence and gates are checked before work starts.';
|
|
370
|
+
await loadAutonomyState();
|
|
371
|
+
});
|
|
372
|
+
});
|
|
373
|
+
$('autonomyRevokeButton').addEventListener('click', event => {
|
|
374
|
+
$('autonomyRevocationDialog')._expectedDigest = view.status?.grant?.digest;
|
|
375
|
+
openDialog('autonomyRevocationDialog', event.currentTarget, 'autonomyRevokerName');
|
|
376
|
+
});
|
|
377
|
+
$('autonomyRevocationForm').addEventListener('submit', event => {
|
|
378
|
+
event.preventDefault(); const name = $('autonomyRevokerName').value.trim();
|
|
379
|
+
if (!name) return;
|
|
380
|
+
perform(async () => {
|
|
381
|
+
await mutation('revoke', { expectedDigest: $('autonomyRevocationDialog')._expectedDigest, revokedBy: name, confirmed: true });
|
|
382
|
+
$('autonomyRevocationDialog').close(); view.draftPreview = null;
|
|
383
|
+
view.notice = 'Grant revoked. Pending work is preserved; no further dispatch under this grant.';
|
|
384
|
+
await loadAutonomyState();
|
|
385
|
+
});
|
|
386
|
+
});
|
|
387
|
+
$('autonomyDispatchForm').addEventListener('submit', event => {
|
|
388
|
+
event.preventDefault();
|
|
389
|
+
if (view.dispatching || view.busy) return;
|
|
390
|
+
const reviewed = view.dispatch;
|
|
391
|
+
if (!reviewed || view.status?.status !== 'current' || view.status.grant?.digest !== reviewed.grantDigest
|
|
392
|
+
|| view.preview?.executable[0]?.intentId !== reviewed.intentId) {
|
|
393
|
+
const message = safeError(staleDecision('autonomy-grant-stale'));
|
|
394
|
+
$('autonomyDispatchDialog').querySelector('[data-autonomy-error]').textContent = message;
|
|
395
|
+
view.notice = message;
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const mode = $('autonomyDispatchMode').value;
|
|
399
|
+
view.dispatching = true;
|
|
400
|
+
view.notice = 'Dispatch request pending. Live work shows the recorded run as soon as it is available.';
|
|
401
|
+
$('autonomyDispatchDialog').close();
|
|
402
|
+
host.showView('live');
|
|
403
|
+
renderAutonomyDecisions(); renderAutonomyLive();
|
|
404
|
+
(async () => {
|
|
405
|
+
try {
|
|
406
|
+
const result = await mutation(mode, { expectedDigest: reviewed.grantDigest,
|
|
407
|
+
provider: reviewed.provider, confirmed: true });
|
|
408
|
+
view.notice = `Run ${result.status}. Inspect Live work for the actual outcome.`;
|
|
409
|
+
await loadAutonomyState();
|
|
410
|
+
} catch (error) {
|
|
411
|
+
view.notice = safeError(error);
|
|
412
|
+
if (error?.status === 409) await loadAutonomyState();
|
|
413
|
+
} finally {
|
|
414
|
+
view.dispatching = false;
|
|
415
|
+
renderAutonomyConfiguration(); renderAutonomyDecisions(); renderAutonomyLive();
|
|
416
|
+
}
|
|
417
|
+
})();
|
|
418
|
+
});
|
|
419
|
+
$('autonomyControlForm').addEventListener('submit', event => {
|
|
420
|
+
event.preventDefault(); if (!view.control) return;
|
|
421
|
+
if (view.control.action === 'resume') {
|
|
422
|
+
const control = view.control;
|
|
423
|
+
if (view.resuming.has(control.runId) || view.busy) return;
|
|
424
|
+
view.resuming.add(control.runId);
|
|
425
|
+
$('autonomyControlDialog').close();
|
|
426
|
+
view.notice = 'Resume request pending. Live work shows the recorded outcome; cancellation remains available.';
|
|
427
|
+
renderAutonomyLive();
|
|
428
|
+
(async () => {
|
|
429
|
+
try { await submitAutonomyControl(control.action, control.revision, control.runId); }
|
|
430
|
+
catch (error) {
|
|
431
|
+
view.notice = safeError(error);
|
|
432
|
+
if (error?.status === 409) await loadAutonomyState();
|
|
433
|
+
} finally {
|
|
434
|
+
view.resuming.delete(control.runId);
|
|
435
|
+
renderAutonomyConfiguration(); renderAutonomyDecisions(); renderAutonomyLive();
|
|
436
|
+
}
|
|
437
|
+
})();
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
perform(async () => { const control = view.control; await submitAutonomyControl(control.action, control.revision, control.runId); $('autonomyControlDialog').close(); });
|
|
441
|
+
});
|
|
442
|
+
$('autonomyAnswerForm').addEventListener('submit', event => {
|
|
443
|
+
event.preventDefault(); if (!view.answer) return;
|
|
444
|
+
const answeredBy = $('autonomyAnswerName').value.trim(), answer = $('autonomyAnswerText').value;
|
|
445
|
+
if (!answeredBy || !answer.trim()) return;
|
|
446
|
+
perform(async () => {
|
|
447
|
+
await mutation('answer', { runId: view.answer.runId, questionId: view.answer.questionId,
|
|
448
|
+
expectedRevision: view.answer.revision, answeredBy, answer, confirmed: true });
|
|
449
|
+
$('autonomyAnswerText').value = ''; $('autonomyAnswerDialog').close();
|
|
450
|
+
view.notice = 'Answer recorded privately. Human approval remains separate.';
|
|
451
|
+
await loadAutonomyState();
|
|
452
|
+
});
|
|
453
|
+
});
|
|
454
|
+
document.querySelectorAll('[data-autonomy-close]').forEach(button => button.addEventListener('click', () => button.closest('dialog').close()));
|
|
455
|
+
document.querySelectorAll('.autonomy-dialog').forEach(dialog => {
|
|
456
|
+
const error = node('p', '', 'autonomy-dialog-error'); error.dataset.autonomyError = '';
|
|
457
|
+
error.setAttribute('role', 'alert'); dialog.querySelector('.action-body').append(error);
|
|
458
|
+
dialog.addEventListener('close', () => {
|
|
459
|
+
renderAutonomyDecisions(); renderAutonomyLive();
|
|
460
|
+
const opener = dialog._autonomyOpener;
|
|
461
|
+
const replacement = dialog._autonomyOpenerId ? $(dialog._autonomyOpenerId)
|
|
462
|
+
: [...document.querySelectorAll('[data-autonomy-focus]')]
|
|
463
|
+
.find(button => button.dataset.autonomyFocus === dialog._autonomyOpenerKey);
|
|
464
|
+
if (opener?.isConnected && opener.getClientRects().length && !opener.disabled) opener.focus();
|
|
465
|
+
else if (replacement?.getClientRects().length && !replacement.disabled) replacement.focus();
|
|
466
|
+
else ($('liveView').hidden ? $('configurationNav') : $('autonomyLiveTitle')).focus();
|
|
467
|
+
});
|
|
468
|
+
});
|
|
469
|
+
renderAutonomyConfiguration(); renderAutonomyDecisions(); renderAutonomyLive();
|
|
470
|
+
}
|
package/public/index.html
CHANGED
|
@@ -53,6 +53,24 @@
|
|
|
53
53
|
<header class="configuration-heading"><h2>Make this dashboard work for you.</h2><p>Keep the everyday tools. Add the extra views when your project needs them.</p></header>
|
|
54
54
|
<form id="dashboardPreferencesForm"><div id="dashboardPreferenceGroups"></div><div class="configuration-save"><button id="saveDashboardPreferences" type="submit" class="primary-action" disabled>Save changes</button><button id="cancelDashboardPreferences" type="button" class="quiet-button">Cancel</button><output id="dashboardPreferenceStatus" aria-live="polite"></output></div></form>
|
|
55
55
|
<p class="configuration-assurance">These settings only change what’s shown. Your standards, required checks and approvals still apply. Turning on a view won’t run a scan, connect a team service or install anything.</p>
|
|
56
|
+
<section class="configuration-group autonomy-configuration" aria-labelledby="autonomyConfigurationTitle">
|
|
57
|
+
<header><div><h3 id="autonomyConfigurationTitle">Delegated delivery</h3><p>Choose an exact pool and limits. EWAI can progress only permitted work between your decisions.</p></div><strong id="autonomyMode" class="autonomy-mode">Checking…</strong></header>
|
|
58
|
+
<div class="autonomy-configuration-body">
|
|
59
|
+
<fieldset class="autonomy-pool-field"><legend>Exact intent pool</legend><p>Only the intents you select can enter this grant.</p><div id="autonomyPool" class="autonomy-pool" aria-live="polite">Loading project intents…</div></fieldset>
|
|
60
|
+
<fieldset class="autonomy-actions-field"><legend>Permitted progress</legend><button type="button" role="checkbox" aria-checked="true" name="autonomyAction" value="begin-harness">Begin a ready intent</button><button type="button" role="checkbox" aria-checked="false" name="autonomyAction" value="prepare-phase">Prepare a governed phase</button><button type="button" role="checkbox" aria-checked="false" name="autonomyAction" value="afk-build">Continue approved Build work</button></fieldset>
|
|
61
|
+
<div class="autonomy-limits">
|
|
62
|
+
<label>Provider<select id="autonomyProvider"><option value="codex">Codex</option><option value="claude">Claude</option><option value="antigravity">Antigravity</option></select></label>
|
|
63
|
+
<label>New grant expires after, hours<input id="autonomyExpiryHours" type="number" min="1" max="24" value="2" inputmode="numeric"></label>
|
|
64
|
+
<label>Run limit, minutes<input id="autonomyRuntimeMinutes" type="number" min="1" max="1440" value="30" inputmode="numeric"></label>
|
|
65
|
+
<label>Operation limit, seconds<input id="autonomyOperationSeconds" type="number" min="1" max="3600" value="900" inputmode="numeric"></label>
|
|
66
|
+
<label>Maximum attempts<input id="autonomyAttempts" type="number" min="1" max="100" value="2" inputmode="numeric"></label>
|
|
67
|
+
</div>
|
|
68
|
+
<p class="autonomy-boundary"><strong>Always yours:</strong> prototype selection, Build approval, Manual QA, risk acceptance, destructive actions, deployment and release. Limits bound runtime and attempts; reported spend is not a guaranteed money cap.</p>
|
|
69
|
+
<div class="autonomy-button-row"><button id="autonomyPreviewButton" type="button" class="secondary-action">Preview selected work</button><button id="autonomyReviewButton" type="button" class="primary-action" disabled>Review and approve grant</button><button id="autonomyRevokeButton" type="button" class="secondary-action danger" disabled>Revoke grant</button></div>
|
|
70
|
+
<p id="autonomyConfigurationStatus" role="status" aria-live="polite">Autonomy is off until you approve a named grant.</p>
|
|
71
|
+
<div id="autonomyProposalSummary" class="autonomy-proposal-summary" aria-live="polite"></div>
|
|
72
|
+
</div>
|
|
73
|
+
</section>
|
|
56
74
|
<section class="configuration-group"><header><h3>Premium personas</h3><p>Manage your licence and installed library.</p></header><div class="configuration-licence"><strong id="configurationLicenceStatus">Checking persona status…</strong><p id="configurationLicenceDetail"></p><button id="managePersonaLicence" type="button" class="quiet-button">Manage licence</button></div></section>
|
|
57
75
|
<section class="configuration-group"><header><h3>Setup and support</h3><p>Open these when you need them, without adding permanent navigation.</p></header><div class="configuration-shortcuts"><button type="button" class="quiet-button" data-view="guided">Project discovery</button><button type="button" class="quiet-button" data-view="intent-studio">Shape an intent</button><button type="button" class="quiet-button" data-view="error-reporting">Diagnostics and support</button></div></section>
|
|
58
76
|
</section>
|
|
@@ -142,6 +160,7 @@
|
|
|
142
160
|
</section>
|
|
143
161
|
</aside>
|
|
144
162
|
</div>
|
|
163
|
+
<section class="autonomy-companion" aria-labelledby="autonomyCompanionTitle"><header><h3 id="autonomyCompanionTitle">Delegated delivery</h3><p id="autonomyCompanionStatus" role="status" aria-live="polite" tabindex="-1">Checking the exact pool and next actions…</p></header><div class="autonomy-columns"><section aria-labelledby="autonomyCanProgressTitle"><h4 id="autonomyCanProgressTitle">Can progress</h4><div id="autonomyCanProgress"></div></section><section aria-labelledby="autonomyNeedsHumanTitle"><h4 id="autonomyNeedsHumanTitle">Needs your decision</h4><div id="autonomyNeedsHuman"></div></section></div><section class="autonomy-blocked-section" aria-labelledby="autonomyBlockedTitle"><h4 id="autonomyBlockedTitle">Not eligible</h4><div id="autonomyBlocked"></div></section></section>
|
|
145
164
|
</section>
|
|
146
165
|
|
|
147
166
|
<section id="portfolioView" class="dashboard-view portfolio-view" hidden aria-labelledby="portfolioTitle">
|
|
@@ -238,6 +257,7 @@
|
|
|
238
257
|
</header>
|
|
239
258
|
<section id="liveSummary" class="live-summary"></section>
|
|
240
259
|
<section id="activeSessions" class="active-grid"></section>
|
|
260
|
+
<section class="autonomy-live" aria-labelledby="autonomyLiveTitle"><header><h3 id="autonomyLiveTitle" tabindex="-1">Delegated runs</h3><p>Actual run outcomes and stopping points. A cancellation request is not a confirmed stop.</p></header><p id="autonomyLiveStatus" role="status" aria-live="polite"></p><div id="autonomyLiveRuns" aria-live="polite">Checking delegated runs…</div></section>
|
|
241
261
|
</section>
|
|
242
262
|
|
|
243
263
|
<section id="knowledgeView" class="dashboard-view knowledge-view" hidden>
|
|
@@ -772,6 +792,12 @@
|
|
|
772
792
|
</form>
|
|
773
793
|
</dialog>
|
|
774
794
|
|
|
795
|
+
<dialog id="autonomyApprovalDialog" class="action-dialog autonomy-dialog" aria-labelledby="autonomyApprovalTitle"><form id="autonomyApprovalForm" class="action-sheet"><header><div><h2 id="autonomyApprovalTitle">Approve this exact grant</h2><p>Review the selected intents, actions, provider and limits. This never approves Build or Manual QA.</p></div><button type="button" class="close-button" data-autonomy-close aria-label="Close grant review">×</button></header><section class="action-body"><div id="autonomyApprovalSummary"></div><label for="autonomyApproverName">Your name</label><input id="autonomyApproverName" required maxlength="120" autocomplete="name"></section><footer><button type="button" class="secondary-action" data-autonomy-close>Keep autonomy off</button><button type="submit" class="primary-action">Approve this scope</button></footer></form></dialog>
|
|
796
|
+
<dialog id="autonomyRevocationDialog" class="action-dialog autonomy-dialog" aria-labelledby="autonomyRevocationTitle"><form id="autonomyRevocationForm" class="action-sheet"><header><div><h2 id="autonomyRevocationTitle">Revoke this grant</h2><p>Further dispatch and result acceptance will stop. An active process may still need cancellation.</p></div><button type="button" class="close-button" data-autonomy-close aria-label="Close revocation">×</button></header><section class="action-body"><label for="autonomyRevokerName">Your name</label><input id="autonomyRevokerName" required maxlength="120" autocomplete="name"></section><footer><button type="button" class="secondary-action" data-autonomy-close>Keep grant</button><button type="submit" class="primary-action danger">Confirm revocation</button></footer></form></dialog>
|
|
797
|
+
<dialog id="autonomyDispatchDialog" class="action-dialog autonomy-dialog" aria-labelledby="autonomyDispatchTitle"><form id="autonomyDispatchForm" class="action-sheet"><header><div><h2 id="autonomyDispatchTitle">Start the next permitted action</h2><p id="autonomyDispatchSummary"></p></div><button type="button" class="close-button" data-autonomy-close aria-label="Close run review">×</button></header><section class="action-body"><p>EWAI rechecks the grant, repository, evidence and current human gates before dispatch.</p><label for="autonomyDispatchMode">Run mode</label><select id="autonomyDispatchMode"><option value="run">Run one bounded action</option><option value="service">Start a local service until a checkpoint</option></select></section><footer><button type="button" class="secondary-action" data-autonomy-close>Not now</button><button type="submit" class="primary-action">Start permitted work</button></footer></form></dialog>
|
|
798
|
+
<dialog id="autonomyControlDialog" class="action-dialog autonomy-dialog" aria-labelledby="autonomyControlTitle"><form id="autonomyControlForm" class="action-sheet"><header><div><h2 id="autonomyControlTitle">Control delegated run</h2><p id="autonomyControlSummary"></p></div><button type="button" class="close-button" data-autonomy-close aria-label="Close run control">×</button></header><section class="action-body"><p id="autonomyControlBoundary"></p></section><footer><button type="button" class="secondary-action" data-autonomy-close>Not now</button><button id="autonomyControlConfirm" type="submit" class="primary-action">Confirm</button></footer></form></dialog>
|
|
799
|
+
<dialog id="autonomyAnswerDialog" class="action-dialog autonomy-dialog" aria-labelledby="autonomyAnswerTitle"><form id="autonomyAnswerForm" class="action-sheet"><header><div><h2 id="autonomyAnswerTitle">Answer a human question</h2><p>Your answer is private run evidence. It cannot approve Build or another human gate.</p></div><button type="button" class="close-button" data-autonomy-close aria-label="Close answer">×</button></header><section class="action-body"><p id="autonomyAnswerSummary" class="autonomy-question-context"></p><label for="autonomyAnswerName">Your name (must match the assigned owner)</label><input id="autonomyAnswerName" required maxlength="120" autocomplete="name"><label for="autonomyAnswerText">Your answer</label><textarea id="autonomyAnswerText" required maxlength="16000"></textarea></section><footer><button type="button" class="secondary-action" data-autonomy-close>Not now</button><button type="submit" class="primary-action">Record answer</button></footer></form></dialog>
|
|
800
|
+
|
|
775
801
|
<dialog id="premiumSetupDialog" class="action-dialog" aria-labelledby="premiumSetupTitle">
|
|
776
802
|
<form id="premiumSetupForm" class="action-sheet premium-setup-form">
|
|
777
803
|
<header><div><h2 id="premiumSetupTitle">Set up premium personas</h2><p>Enter the key from My Account or your team invitation. We'll verify your licence and install the pack now.</p></div><button id="closePremiumSetup" type="button" class="close-button" aria-label="Close persona setup">×</button></header>
|
package/public/styles.css
CHANGED
|
@@ -2691,3 +2691,67 @@ button:focus-visible, input:focus-visible, select:focus-visible, textarea:focus-
|
|
|
2691
2691
|
.configuration-heading h2 { font-size: 25px; }
|
|
2692
2692
|
.configuration-group header, .configuration-setting, .configuration-licence, .configuration-shortcuts { padding: 19px; }
|
|
2693
2693
|
}
|
|
2694
|
+
|
|
2695
|
+
/* Governed autonomy sits inside the existing views, never in a new navigation lane. */
|
|
2696
|
+
.autonomy-configuration > header { display: flex; align-items: flex-start; justify-content: space-between; gap: 18px; }
|
|
2697
|
+
.autonomy-mode { flex: 0 0 auto; padding: 7px 10px; border-radius: 4px; background: var(--canvas); color: var(--muted); font-size: 11px; }
|
|
2698
|
+
.autonomy-mode.delegated { background: var(--green-soft); color: var(--green); }
|
|
2699
|
+
.autonomy-mode.revoked { background: var(--red-soft); color: var(--red); }
|
|
2700
|
+
.autonomy-configuration-body { display: grid; gap: 17px; min-width: 0; padding: 24px; }
|
|
2701
|
+
.autonomy-configuration fieldset { min-width: 0; margin: 0; padding: 15px; border: 1px solid var(--line); border-radius: 5px; }
|
|
2702
|
+
.autonomy-configuration legend { padding: 0 5px; font-weight: 750; }
|
|
2703
|
+
.autonomy-configuration fieldset > p { margin: 0 0 12px; font-size: 12px; }
|
|
2704
|
+
.autonomy-pool { display: grid; gap: 8px; margin-top: 12px; }
|
|
2705
|
+
.autonomy-pool-choice { display: grid; grid-template-columns: 20px minmax(0, 1fr); column-gap: 9px; align-items: center; min-height: 44px; padding: 6px 10px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface); text-align: left; cursor: pointer; }
|
|
2706
|
+
.autonomy-pool-choice[aria-checked="true"] { border-color: var(--green); background: var(--green-soft); }
|
|
2707
|
+
.autonomy-pool-mark { grid-row: span 2; display: grid; place-items: center; width: 18px; height: 18px; border: 1px solid var(--line-strong); border-radius: 3px; color: transparent; font-size: 12px; }
|
|
2708
|
+
.autonomy-pool-choice[aria-checked="true"] .autonomy-pool-mark { border-color: var(--green); background: var(--green); color: white; }
|
|
2709
|
+
.autonomy-pool-choice span, .autonomy-pool-choice small { min-width: 0; overflow-wrap: anywhere; }
|
|
2710
|
+
.autonomy-pool-choice small { color: var(--muted); }
|
|
2711
|
+
.autonomy-actions-field { display: grid; gap: 7px; }
|
|
2712
|
+
.autonomy-actions-field button { min-height: 44px; padding: 9px 11px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface); text-align: left; cursor: pointer; }
|
|
2713
|
+
.autonomy-actions-field button[aria-checked="true"] { border-color: var(--green); background: var(--green-soft); color: var(--green); font-weight: 750; }
|
|
2714
|
+
.autonomy-limits { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 13px; }
|
|
2715
|
+
.autonomy-limits label { display: grid; gap: 5px; font-weight: 700; }
|
|
2716
|
+
.autonomy-limits input, .autonomy-limits select, .autonomy-dialog input, .autonomy-dialog select, .autonomy-dialog textarea { width: 100%; min-width: 0; min-height: 44px; padding: 9px 10px; border: 1px solid var(--line-strong); border-radius: 5px; background: var(--surface); color: var(--ink); }
|
|
2717
|
+
.autonomy-dialog textarea { min-height: 120px; resize: vertical; }
|
|
2718
|
+
.autonomy-boundary { padding: 13px 15px; border-left: 4px solid var(--amber); background: var(--amber-soft); color: var(--ink) !important; }
|
|
2719
|
+
.autonomy-button-row, .autonomy-card-actions { display: flex; flex-wrap: wrap; gap: 9px; }
|
|
2720
|
+
.autonomy-button-row button, .autonomy-card-actions button, .autonomy-dialog button { min-height: 44px; font-size: 12px; }
|
|
2721
|
+
.autonomy-proposal-summary { padding: 13px 15px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-2); overflow-wrap: anywhere; }
|
|
2722
|
+
#autonomyConfigurationStatus { font-weight: 700; }
|
|
2723
|
+
.autonomy-companion, .autonomy-live { margin: 20px 24px 28px; padding: 22px; border: 1px solid var(--line); border-radius: 6px; background: var(--surface); }
|
|
2724
|
+
.autonomy-companion > header, .autonomy-live > header { margin-bottom: 18px; }
|
|
2725
|
+
.autonomy-companion h3, .autonomy-live h3 { margin: 0 0 6px; font-size: 19px; }
|
|
2726
|
+
.autonomy-companion h4, .autonomy-live h4 { margin: 0 0 9px; font-size: 14px; }
|
|
2727
|
+
.autonomy-companion header p, .autonomy-live header p { margin: 0; color: var(--muted); line-height: 1.5; }
|
|
2728
|
+
.autonomy-columns { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px; }
|
|
2729
|
+
.autonomy-columns > section, .autonomy-blocked-section { min-width: 0; padding: 16px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface-2); }
|
|
2730
|
+
.autonomy-columns > section:nth-child(2) { border-top: 3px solid var(--amber); }
|
|
2731
|
+
.autonomy-blocked-section { margin-top: 16px; }
|
|
2732
|
+
.autonomy-candidate, .autonomy-run, .autonomy-empty { min-width: 0; padding: 15px; border: 1px solid var(--line); border-radius: 5px; background: var(--surface); overflow-wrap: anywhere; }
|
|
2733
|
+
.autonomy-candidate + .autonomy-candidate, .autonomy-run + .autonomy-run { margin-top: 9px; }
|
|
2734
|
+
.autonomy-candidate h5 { margin: 0 0 4px; font-size: 14px; }
|
|
2735
|
+
.autonomy-candidate small, .autonomy-run small { color: var(--muted); overflow-wrap: anywhere; }
|
|
2736
|
+
.autonomy-candidate p, .autonomy-run p, .autonomy-empty p { margin: 10px 0; line-height: 1.5; }
|
|
2737
|
+
.autonomy-question-context { padding: 10px 12px; border-left: 3px solid var(--amber); background: var(--amber-soft); color: var(--ink); overflow-wrap: anywhere; }
|
|
2738
|
+
.autonomy-reasons { color: var(--amber); font-weight: 700; }
|
|
2739
|
+
.autonomy-candidate .autonomy-card-actions, .autonomy-run .autonomy-card-actions { margin-top: 12px; }
|
|
2740
|
+
.autonomy-intent-detail { margin-top: 18px; padding: 16px; border-left: 4px solid var(--green); background: var(--surface-2); }
|
|
2741
|
+
.autonomy-intent-detail h3 { margin: 0 0 7px; }
|
|
2742
|
+
.autonomy-intent-detail p { margin: 7px 0; overflow-wrap: anywhere; }
|
|
2743
|
+
.autonomy-dialog .action-body { display: grid; gap: 9px; }
|
|
2744
|
+
.autonomy-dialog .action-body label { font-weight: 700; }
|
|
2745
|
+
.autonomy-dialog .action-body p { margin: 0 0 8px; }
|
|
2746
|
+
.autonomy-dialog .close-button { width: 44px; min-width: 44px; height: 44px; padding: 0; }
|
|
2747
|
+
.autonomy-dialog-error:not(:empty) { padding: 10px 12px; border-left: 3px solid var(--red); background: var(--red-soft); color: var(--red); }
|
|
2748
|
+
.autonomy-dialog :focus-visible, .autonomy-configuration :focus-visible, .autonomy-companion :focus-visible, .autonomy-live :focus-visible { outline: 3px solid var(--blue); outline-offset: 2px; }
|
|
2749
|
+
@media (max-width: 720px) {
|
|
2750
|
+
.autonomy-configuration > header { flex-direction: column; }
|
|
2751
|
+
.autonomy-configuration-body { padding: 18px; }
|
|
2752
|
+
.autonomy-limits, .autonomy-columns { grid-template-columns: minmax(0, 1fr); }
|
|
2753
|
+
.autonomy-companion, .autonomy-live { margin: 16px 12px; padding: 16px; }
|
|
2754
|
+
.autonomy-button-row { flex-direction: column; }
|
|
2755
|
+
.autonomy-button-row button { width: 100%; }
|
|
2756
|
+
.autonomy-dialog .action-sheet > footer { flex-wrap: wrap; }
|
|
2757
|
+
}
|