tryassay 0.21.2 → 0.22.1
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/demo/.claude/.truth_last_prompt +1 -0
- package/demo/.claude/truth_status +1 -0
- package/demo/css/style.css +840 -0
- package/demo/index.html +78 -0
- package/demo/js/chat.js +535 -0
- package/demo/js/code-panel.js +206 -0
- package/demo/js/preview.js +456 -0
- package/demo/js/sse-client.js +600 -0
- package/demo/js/state.js +172 -0
- package/demo/js/timeline.js +80 -0
- package/dist/api/server.d.ts +3 -0
- package/dist/api/server.js +127 -20
- package/dist/api/server.js.map +1 -1
- package/dist/cli.js +11 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/assess.d.ts +2 -0
- package/dist/commands/assess.js +132 -164
- package/dist/commands/assess.js.map +1 -1
- package/dist/commands/demo.d.ts +5 -0
- package/dist/commands/demo.js +357 -0
- package/dist/commands/demo.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-quick-test.d.ts +6 -0
- package/dist/lib/__tests__/arithmetic-quick-test.js +197 -0
- package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.d.ts +13 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.js +284 -0
- package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -0
- package/dist/lib/__tests__/arithmetic-value-demo.d.ts +10 -0
- package/dist/lib/__tests__/arithmetic-value-demo.js +193 -0
- package/dist/lib/__tests__/arithmetic-value-demo.js.map +1 -0
- package/dist/lib/__tests__/flow-to-claims.test.d.ts +1 -0
- package/dist/lib/__tests__/flow-to-claims.test.js +91 -0
- package/dist/lib/__tests__/flow-to-claims.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.d.ts +9 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +391 -0
- package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.d.ts +7 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.js +318 -0
- package/dist/lib/__tests__/formal-verifier-arithmetic.test.js.map +1 -0
- package/dist/lib/__tests__/intent-extractor.test.d.ts +1 -0
- package/dist/lib/__tests__/intent-extractor.test.js +97 -0
- package/dist/lib/__tests__/intent-extractor.test.js.map +1 -0
- package/dist/lib/__tests__/intent-reviewer.test.d.ts +1 -0
- package/dist/lib/__tests__/intent-reviewer.test.js +55 -0
- package/dist/lib/__tests__/intent-reviewer.test.js.map +1 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.d.ts +11 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js +224 -0
- package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -0
- package/dist/lib/anthropic.js +25 -33
- package/dist/lib/anthropic.js.map +1 -1
- package/dist/lib/assessment-reporter.js +9 -13
- package/dist/lib/assessment-reporter.js.map +1 -1
- package/dist/lib/claim-extractor.js +10 -19
- package/dist/lib/claim-extractor.js.map +1 -1
- package/dist/lib/code-verifier.js +16 -36
- package/dist/lib/code-verifier.js.map +1 -1
- package/dist/lib/constraint-engine.js +10 -19
- package/dist/lib/constraint-engine.js.map +1 -1
- package/dist/lib/formal-verifier.d.ts +1 -1
- package/dist/lib/formal-verifier.js +454 -0
- package/dist/lib/formal-verifier.js.map +1 -1
- package/dist/lib/guided-generator.js +19 -37
- package/dist/lib/guided-generator.js.map +1 -1
- package/dist/lib/intent-extractor.d.ts +47 -0
- package/dist/lib/intent-extractor.js +427 -0
- package/dist/lib/intent-extractor.js.map +1 -0
- package/dist/lib/intent-reviewer.d.ts +14 -0
- package/dist/lib/intent-reviewer.js +148 -0
- package/dist/lib/intent-reviewer.js.map +1 -0
- package/dist/lib/intent-types.d.ts +89 -0
- package/dist/lib/intent-types.js +5 -0
- package/dist/lib/intent-types.js.map +1 -0
- package/dist/lib/inventory-extractor.js +9 -22
- package/dist/lib/inventory-extractor.js.map +1 -1
- package/dist/lib/llm-provider.d.ts +23 -0
- package/dist/lib/llm-provider.js +130 -0
- package/dist/lib/llm-provider.js.map +1 -0
- package/dist/lib/remediator.js +20 -28
- package/dist/lib/remediator.js.map +1 -1
- package/dist/lib/requirements-generator.js +14 -19
- package/dist/lib/requirements-generator.js.map +1 -1
- package/dist/lib/spec-synthesizer.js +10 -19
- package/dist/lib/spec-synthesizer.js.map +1 -1
- package/dist/runtime/agents/planner-agent.d.ts +5 -2
- package/dist/runtime/agents/planner-agent.js +232 -1
- package/dist/runtime/agents/planner-agent.js.map +1 -1
- package/dist/runtime/app-create-orchestrator.d.ts +9 -1
- package/dist/runtime/app-create-orchestrator.js +265 -87
- package/dist/runtime/app-create-orchestrator.js.map +1 -1
- package/dist/runtime/check-catalog.js +5 -3
- package/dist/runtime/check-catalog.js.map +1 -1
- package/dist/runtime/check-definitions.d.ts +10 -0
- package/dist/runtime/check-definitions.js +52 -2
- package/dist/runtime/check-definitions.js.map +1 -1
- package/dist/runtime/composition-verifier.js +8 -12
- package/dist/runtime/composition-verifier.js.map +1 -1
- package/dist/runtime/gap-detector.js +8 -10
- package/dist/runtime/gap-detector.js.map +1 -1
- package/dist/runtime/input-validator.d.ts +7 -0
- package/dist/runtime/input-validator.js +162 -0
- package/dist/runtime/input-validator.js.map +1 -0
- package/dist/runtime/model-router.d.ts +10 -0
- package/dist/runtime/model-router.js +42 -0
- package/dist/runtime/model-router.js.map +1 -0
- package/dist/runtime/pattern-extractor.js +8 -10
- package/dist/runtime/pattern-extractor.js.map +1 -1
- package/dist/runtime/planner.js +11 -16
- package/dist/runtime/planner.js.map +1 -1
- package/dist/runtime/prompt-guard.d.ts +2 -0
- package/dist/runtime/prompt-guard.js +180 -0
- package/dist/runtime/prompt-guard.js.map +1 -0
- package/dist/runtime/prompt-safety-analyzer.js +8 -13
- package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
- package/dist/runtime/reasoner.js +19 -33
- package/dist/runtime/reasoner.js.map +1 -1
- package/dist/runtime/rule-meta-verifier.js +9 -11
- package/dist/runtime/rule-meta-verifier.js.map +1 -1
- package/dist/runtime/safe-executor.d.ts +23 -0
- package/dist/runtime/safe-executor.js +151 -0
- package/dist/runtime/safe-executor.js.map +1 -0
- package/dist/runtime/specialized-agent.js +10 -14
- package/dist/runtime/specialized-agent.js.map +1 -1
- package/dist/runtime/strategy-library.js +8 -10
- package/dist/runtime/strategy-library.js.map +1 -1
- package/dist/runtime/supabase-experience-store.js.map +1 -1
- package/dist/runtime/supabase-provisioner.d.ts +35 -0
- package/dist/runtime/supabase-provisioner.js +192 -0
- package/dist/runtime/supabase-provisioner.js.map +1 -0
- package/dist/runtime/types.d.ts +116 -0
- package/dist/sdk/forward-verify.js +16 -33
- package/dist/sdk/forward-verify.js.map +1 -1
- package/package.json +2 -1
|
@@ -0,0 +1,600 @@
|
|
|
1
|
+
// sse-client.js — EventSource to AppState bridge
|
|
2
|
+
// Connects to the Assay API SSE stream and maps events to state updates
|
|
3
|
+
|
|
4
|
+
import { configureChat, addArchitectMessage, lockChat, unlockChat } from './chat.js';
|
|
5
|
+
import { downloadProject, collectProjectFiles } from './preview.js';
|
|
6
|
+
|
|
7
|
+
const AppState = window.AppState;
|
|
8
|
+
|
|
9
|
+
let eventSource = null;
|
|
10
|
+
let apiBase = '';
|
|
11
|
+
let apiKey = '';
|
|
12
|
+
|
|
13
|
+
function autoApprovePlan(appId) {
|
|
14
|
+
const headers = { 'Content-Type': 'application/json' };
|
|
15
|
+
if (apiKey) headers['Authorization'] = `Bearer ${apiKey}`;
|
|
16
|
+
fetch(`${apiBase}/api/v1/app/${appId}/approve`, {
|
|
17
|
+
method: 'POST',
|
|
18
|
+
headers,
|
|
19
|
+
body: JSON.stringify({ decision: 'approved' }),
|
|
20
|
+
}).then(res => {
|
|
21
|
+
if (res.ok) {
|
|
22
|
+
AppState.addLog({ level: 'info', message: 'Plan approved — building...' });
|
|
23
|
+
} else {
|
|
24
|
+
console.warn('[SSE] Auto-approve failed:', res.status);
|
|
25
|
+
}
|
|
26
|
+
}).catch(err => {
|
|
27
|
+
console.error('[SSE] Auto-approve error:', err);
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function showCompletionOverlay(data) {
|
|
32
|
+
const overlay = document.getElementById('completion-overlay');
|
|
33
|
+
if (!overlay) return;
|
|
34
|
+
|
|
35
|
+
const m = AppState.get().metrics;
|
|
36
|
+
const appName = data.appName || 'Application';
|
|
37
|
+
const isSuccess = data.status === 'completed';
|
|
38
|
+
const status = isSuccess ? 'Build Complete' : 'Build Finished with Issues';
|
|
39
|
+
const statusClass = isSuccess ? 'success' : 'partial';
|
|
40
|
+
|
|
41
|
+
const clockVal = document.getElementById('clock-value');
|
|
42
|
+
const totalTime = clockVal ? clockVal.textContent : '';
|
|
43
|
+
|
|
44
|
+
overlay.innerHTML = `
|
|
45
|
+
<div class="completion-card">
|
|
46
|
+
<button class="completion-close-btn" id="completion-close" title="Close">\u00d7</button>
|
|
47
|
+
<div class="completion-icon ${statusClass}">${isSuccess ? '\u2713' : '\u26a0'}</div>
|
|
48
|
+
<div class="completion-header">${status}</div>
|
|
49
|
+
<div class="completion-app-name">${appName}</div>
|
|
50
|
+
<div class="completion-metrics">
|
|
51
|
+
<span>${m.filesGenerated} files</span>
|
|
52
|
+
<span>${m.claimsVerified} claims verified</span>
|
|
53
|
+
<span>${m.claimsFailed} caught</span>
|
|
54
|
+
${totalTime ? `<span>${totalTime} total</span>` : ''}
|
|
55
|
+
</div>
|
|
56
|
+
<div class="completion-actions">
|
|
57
|
+
<button class="completion-action-btn completion-download-btn" id="completion-download">
|
|
58
|
+
<span class="completion-action-icon">\u2913</span>
|
|
59
|
+
Download Project (.zip)
|
|
60
|
+
</button>
|
|
61
|
+
<button class="completion-action-btn completion-github-btn" id="completion-github">
|
|
62
|
+
<span class="completion-action-icon">\u2197</span>
|
|
63
|
+
Push to GitHub
|
|
64
|
+
</button>
|
|
65
|
+
</div>
|
|
66
|
+
<div class="completion-status" id="completion-status"></div>
|
|
67
|
+
</div>
|
|
68
|
+
`;
|
|
69
|
+
overlay.classList.add('visible');
|
|
70
|
+
|
|
71
|
+
// Close button — dismisses modal, reveals preview + chat
|
|
72
|
+
document.getElementById('completion-close')?.addEventListener('click', () => {
|
|
73
|
+
overlay.classList.remove('visible');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
// Download button — calls existing zip download from preview.js
|
|
77
|
+
document.getElementById('completion-download')?.addEventListener('click', () => {
|
|
78
|
+
downloadProject();
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
// GitHub button — starts GitHub export flow
|
|
82
|
+
document.getElementById('completion-github')?.addEventListener('click', () => {
|
|
83
|
+
startGitHubExport(appName);
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* GitHub export flow:
|
|
89
|
+
* 1. Check if user is authenticated via GitHub OAuth
|
|
90
|
+
* 2. If not, open OAuth popup
|
|
91
|
+
* 3. Once authenticated, show repo form and push
|
|
92
|
+
*/
|
|
93
|
+
async function startGitHubExport(appName) {
|
|
94
|
+
const statusEl = document.getElementById('completion-status');
|
|
95
|
+
const githubBtn = document.getElementById('completion-github');
|
|
96
|
+
|
|
97
|
+
// GitHub auth endpoints live on the UI server (same origin), not the API server
|
|
98
|
+
const uiOrigin = window.location.origin;
|
|
99
|
+
|
|
100
|
+
// Check auth status
|
|
101
|
+
if (statusEl) statusEl.textContent = 'Checking GitHub connection...';
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const res = await fetch(`${uiOrigin}/api/v1/auth/github/status`);
|
|
105
|
+
const authData = await res.json();
|
|
106
|
+
|
|
107
|
+
if (!authData.configured) {
|
|
108
|
+
if (statusEl) statusEl.textContent = 'GitHub not configured. Set GITHUB_CLIENT_ID and GITHUB_CLIENT_SECRET env vars, then restart the demo.';
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (authData.authenticated) {
|
|
113
|
+
showGitHubForm(appName, authData.username);
|
|
114
|
+
} else {
|
|
115
|
+
// Open OAuth popup
|
|
116
|
+
if (statusEl) statusEl.textContent = 'Opening GitHub authorization...';
|
|
117
|
+
const popup = window.open(
|
|
118
|
+
`${uiOrigin}/api/v1/auth/github`,
|
|
119
|
+
'github-oauth',
|
|
120
|
+
'width=600,height=700,menubar=no,toolbar=no'
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
// Poll for auth completion
|
|
124
|
+
const pollAuth = setInterval(async () => {
|
|
125
|
+
try {
|
|
126
|
+
if (popup && popup.closed) {
|
|
127
|
+
clearInterval(pollAuth);
|
|
128
|
+
const checkRes = await fetch(`${uiOrigin}/api/v1/auth/github/status`);
|
|
129
|
+
const checkData = await checkRes.json();
|
|
130
|
+
if (checkData.authenticated) {
|
|
131
|
+
showGitHubForm(appName, checkData.username);
|
|
132
|
+
} else {
|
|
133
|
+
if (statusEl) statusEl.textContent = 'GitHub authorization cancelled. You can still download the zip.';
|
|
134
|
+
}
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
} catch { /* keep polling */ }
|
|
138
|
+
}, 1000);
|
|
139
|
+
}
|
|
140
|
+
} catch (err) {
|
|
141
|
+
console.error('[GitHub] Auth check failed:', err);
|
|
142
|
+
if (statusEl) statusEl.textContent = 'GitHub connection unavailable. Download the zip instead.';
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function showGitHubForm(appName, username) {
|
|
147
|
+
const statusEl = document.getElementById('completion-status');
|
|
148
|
+
if (!statusEl) return;
|
|
149
|
+
|
|
150
|
+
const slugName = appName
|
|
151
|
+
.toLowerCase()
|
|
152
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
153
|
+
.replace(/^-|-$/g, '') || 'my-app';
|
|
154
|
+
|
|
155
|
+
statusEl.innerHTML = `
|
|
156
|
+
<div class="github-form">
|
|
157
|
+
<div class="github-form-row">
|
|
158
|
+
<label>Repository</label>
|
|
159
|
+
<input type="text" id="github-repo-name" value="${slugName}" placeholder="repo-name">
|
|
160
|
+
</div>
|
|
161
|
+
<div class="github-form-row">
|
|
162
|
+
<label>Visibility</label>
|
|
163
|
+
<label class="github-toggle">
|
|
164
|
+
<input type="checkbox" id="github-private" checked>
|
|
165
|
+
<span>Private</span>
|
|
166
|
+
</label>
|
|
167
|
+
</div>
|
|
168
|
+
<div class="github-form-row">
|
|
169
|
+
<span class="github-user">Pushing as ${username}</span>
|
|
170
|
+
<button class="completion-action-btn completion-github-push-btn" id="github-push">Create & Push</button>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
`;
|
|
174
|
+
|
|
175
|
+
document.getElementById('github-push')?.addEventListener('click', () => {
|
|
176
|
+
executeGitHubPush();
|
|
177
|
+
});
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function executeGitHubPush() {
|
|
181
|
+
const repoNameEl = document.getElementById('github-repo-name');
|
|
182
|
+
const privateEl = document.getElementById('github-private');
|
|
183
|
+
const pushBtn = document.getElementById('github-push');
|
|
184
|
+
const statusEl = document.getElementById('completion-status');
|
|
185
|
+
|
|
186
|
+
if (!repoNameEl) return;
|
|
187
|
+
|
|
188
|
+
const repoName = repoNameEl.value.trim();
|
|
189
|
+
if (!repoName) return;
|
|
190
|
+
|
|
191
|
+
const isPrivate = privateEl?.checked ?? true;
|
|
192
|
+
|
|
193
|
+
if (pushBtn) {
|
|
194
|
+
pushBtn.disabled = true;
|
|
195
|
+
pushBtn.textContent = 'Creating repo...';
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
// Collect files from WebContainer
|
|
200
|
+
const files = await collectProjectFiles();
|
|
201
|
+
if (files.length === 0) {
|
|
202
|
+
if (statusEl) statusEl.innerHTML = '<span class="github-error">No files found in project.</span>';
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
if (pushBtn) pushBtn.textContent = `Pushing ${files.length} files...`;
|
|
207
|
+
|
|
208
|
+
// Export endpoint lives on the UI server (same origin)
|
|
209
|
+
const uiOrigin = window.location.origin;
|
|
210
|
+
const res = await fetch(`${uiOrigin}/api/v1/app/${window.__appId}/export/github`, {
|
|
211
|
+
method: 'POST',
|
|
212
|
+
headers: { 'Content-Type': 'application/json' },
|
|
213
|
+
body: JSON.stringify({ repoName, private: isPrivate, files }),
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
const result = await res.json();
|
|
217
|
+
|
|
218
|
+
if (result.success && result.repoUrl) {
|
|
219
|
+
if (statusEl) {
|
|
220
|
+
statusEl.innerHTML = `
|
|
221
|
+
<div class="github-success">
|
|
222
|
+
Pushed to <a href="${result.repoUrl}" target="_blank" class="github-link">${result.repoUrl}</a>
|
|
223
|
+
</div>
|
|
224
|
+
`;
|
|
225
|
+
}
|
|
226
|
+
} else {
|
|
227
|
+
if (statusEl) {
|
|
228
|
+
statusEl.innerHTML = `<span class="github-error">${result.error || 'Push failed. Try downloading the zip instead.'}</span>`;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
} catch (err) {
|
|
232
|
+
console.error('[GitHub] Push failed:', err);
|
|
233
|
+
if (statusEl) {
|
|
234
|
+
statusEl.innerHTML = `<span class="github-error">Push failed: ${err.message}</span>`;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
if (pushBtn) {
|
|
239
|
+
pushBtn.disabled = false;
|
|
240
|
+
pushBtn.textContent = 'Create & Push';
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Append a compact activity item to the chat messages panel.
|
|
246
|
+
* Used during build to show real-time progress.
|
|
247
|
+
*/
|
|
248
|
+
function appendActivityItem(icon, text, className) {
|
|
249
|
+
const messagesEl = document.getElementById('chat-messages');
|
|
250
|
+
if (!messagesEl) return;
|
|
251
|
+
|
|
252
|
+
const item = document.createElement('div');
|
|
253
|
+
item.className = `activity-item ${className || ''}`;
|
|
254
|
+
|
|
255
|
+
const iconSpan = document.createElement('span');
|
|
256
|
+
iconSpan.className = 'activity-icon';
|
|
257
|
+
iconSpan.textContent = icon;
|
|
258
|
+
item.appendChild(iconSpan);
|
|
259
|
+
|
|
260
|
+
const textSpan = document.createElement('span');
|
|
261
|
+
textSpan.className = 'activity-text';
|
|
262
|
+
textSpan.textContent = text;
|
|
263
|
+
item.appendChild(textSpan);
|
|
264
|
+
|
|
265
|
+
messagesEl.appendChild(item);
|
|
266
|
+
messagesEl.scrollTop = messagesEl.scrollHeight;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// Track whether completion overlay has been shown
|
|
270
|
+
let completionShown = false;
|
|
271
|
+
|
|
272
|
+
function handleSSEEvent(type, data) {
|
|
273
|
+
switch (type) {
|
|
274
|
+
case 'connected': {
|
|
275
|
+
console.log('[SSE] Stream connected:', data);
|
|
276
|
+
AppState.addLog({ level: 'info', message: `Connected to session: ${data.sessionId || 'unknown'}` });
|
|
277
|
+
completionShown = false;
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
case 'progress': {
|
|
281
|
+
// The orchestrator emits AppCreateProgress with a phase object
|
|
282
|
+
const phase = data.phase;
|
|
283
|
+
const phaseName = typeof phase === 'string' ? phase : phase?.phase;
|
|
284
|
+
if (phaseName) {
|
|
285
|
+
AppState.update({ phase: phaseName });
|
|
286
|
+
|
|
287
|
+
// Activity feed: show phase changes
|
|
288
|
+
const featureName = phase.featureName || phase.featureId || data.currentFeature;
|
|
289
|
+
const phaseLabel = phaseName.replace(/_/g, ' ');
|
|
290
|
+
if (phaseName === 'provisioning_supabase') {
|
|
291
|
+
appendActivityItem('\u2601', 'Provisioning Supabase database...', 'activity-phase');
|
|
292
|
+
} else if (phaseName === 'supabase_migrations') {
|
|
293
|
+
appendActivityItem('\u2601', 'Pushing database migrations...', 'activity-phase');
|
|
294
|
+
} else if (featureName) {
|
|
295
|
+
appendActivityItem('\u25b6', `${phaseLabel} \u2014 ${featureName}`, 'activity-phase');
|
|
296
|
+
} else if (!['completed', 'failed'].includes(phaseName)) {
|
|
297
|
+
appendActivityItem('\u25b6', phaseLabel, 'activity-phase');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Extract feature info for voice narration
|
|
301
|
+
if (phase.featureId || phase.featureName || data.currentFeature) {
|
|
302
|
+
AppState.update({
|
|
303
|
+
currentTask: {
|
|
304
|
+
name: phase.featureName || phase.featureId || data.currentFeature,
|
|
305
|
+
status: 'in_progress'
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// If phase reports counts, update metrics
|
|
311
|
+
if (data.totalFeatures) {
|
|
312
|
+
AppState.update({
|
|
313
|
+
metrics: {
|
|
314
|
+
totalTasks: data.totalFeatures,
|
|
315
|
+
tasksComplete: data.completedFeatures?.length || 0,
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Fallback: if progress says completed/failed but we haven't shown the overlay yet,
|
|
321
|
+
// wait briefly for the 'complete' event, then show overlay from whatever data we have
|
|
322
|
+
if ((phaseName === 'completed' || phaseName === 'failed') && !completionShown) {
|
|
323
|
+
setTimeout(() => {
|
|
324
|
+
if (!completionShown) {
|
|
325
|
+
console.log('[SSE] Showing completion overlay from progress fallback');
|
|
326
|
+
unlockChat();
|
|
327
|
+
showCompletionOverlay({
|
|
328
|
+
status: phaseName,
|
|
329
|
+
appName: data.appName || data.currentFeature || 'Application',
|
|
330
|
+
});
|
|
331
|
+
completionShown = true;
|
|
332
|
+
disconnect();
|
|
333
|
+
}
|
|
334
|
+
}, 2000);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
break;
|
|
338
|
+
}
|
|
339
|
+
case 'task_update': {
|
|
340
|
+
const existing = AppState.get().tasks.find(t => t.id === data.id);
|
|
341
|
+
if (existing) {
|
|
342
|
+
AppState.updateTask(data.id, data);
|
|
343
|
+
} else {
|
|
344
|
+
AppState.addTask(data);
|
|
345
|
+
}
|
|
346
|
+
// Activity feed: show task completion
|
|
347
|
+
if (data.status === 'completed' && data.claims) {
|
|
348
|
+
const passed = data.claims.passed || data.claims.totalClaims - (data.claims.failed || 0) || 0;
|
|
349
|
+
const failed = data.claims.failed || 0;
|
|
350
|
+
appendActivityItem('\u2713', `${data.name || data.id} (${passed} claims, ${failed} failed)`, failed > 0 ? 'activity-warn' : 'activity-success');
|
|
351
|
+
}
|
|
352
|
+
// Extract per-feature claim counts into verification metrics
|
|
353
|
+
if (data.claims) {
|
|
354
|
+
const c = data.claims;
|
|
355
|
+
const passed = c.passed || c.totalClaims - (c.failed || 0) || 0;
|
|
356
|
+
const failed = c.failed || 0;
|
|
357
|
+
for (let i = 0; i < passed; i++) {
|
|
358
|
+
AppState.addVerification({ claim: `${data.name || data.id}: claim ${i + 1}`, result: 'pass', method: 'formal' });
|
|
359
|
+
}
|
|
360
|
+
for (let i = 0; i < failed; i++) {
|
|
361
|
+
AppState.addVerification({ claim: `${data.name || data.id}: failed claim ${i + 1}`, result: 'fail', method: 'formal' });
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
break;
|
|
365
|
+
}
|
|
366
|
+
case 'verification': {
|
|
367
|
+
// Activity feed: show verification results
|
|
368
|
+
if (data.passed_count !== undefined || data.failed_count !== undefined) {
|
|
369
|
+
const p = data.passed_count || 0;
|
|
370
|
+
const f = data.failed_count || 0;
|
|
371
|
+
appendActivityItem('\u2714', `Verification: ${p} passed, ${f} failed`, f > 0 ? 'activity-warn' : 'activity-success');
|
|
372
|
+
}
|
|
373
|
+
// Add the phase-level verification result
|
|
374
|
+
AppState.addVerification({
|
|
375
|
+
claim: data.claim,
|
|
376
|
+
result: data.passed ? 'pass' : 'fail',
|
|
377
|
+
method: data.method || data.verification_method || 'llm'
|
|
378
|
+
});
|
|
379
|
+
// If it includes passed/failed counts, synthesize individual claim metrics
|
|
380
|
+
if (data.passed_count > 0 || data.failed_count > 0) {
|
|
381
|
+
for (let i = 0; i < (data.passed_count || 0); i++) {
|
|
382
|
+
AppState.addVerification({ claim: `${data.phase}: check ${i + 1}`, result: 'pass', method: 'formal' });
|
|
383
|
+
}
|
|
384
|
+
for (let i = 0; i < (data.failed_count || 0); i++) {
|
|
385
|
+
AppState.addVerification({ claim: `${data.phase}: failed check ${i + 1}`, result: 'fail', method: 'formal' });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
break;
|
|
389
|
+
}
|
|
390
|
+
case 'code_generated': {
|
|
391
|
+
AppState.addCodeFile({
|
|
392
|
+
path: data.path,
|
|
393
|
+
language: data.language || 'typescript',
|
|
394
|
+
content: data.content
|
|
395
|
+
});
|
|
396
|
+
// Activity feed: show generated file
|
|
397
|
+
appendActivityItem('+', data.path, 'activity-file');
|
|
398
|
+
const previewPanel = document.getElementById('preview-panel');
|
|
399
|
+
if (previewPanel && !previewPanel.classList.contains('live')) {
|
|
400
|
+
previewPanel.classList.add('live');
|
|
401
|
+
}
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
case 'agent_status': {
|
|
405
|
+
const agents = Array.isArray(data.agents) ? data.agents : (data.name ? [data] : []);
|
|
406
|
+
for (const agent of agents) {
|
|
407
|
+
const existing = AppState.get().agents.find(a => a.name === agent.name);
|
|
408
|
+
if (existing) {
|
|
409
|
+
AppState.updateAgent(agent.name, agent);
|
|
410
|
+
} else {
|
|
411
|
+
AppState.addAgent(agent);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case 'plan_questions': {
|
|
417
|
+
AppState.update({ phase: 'plan_questioning' });
|
|
418
|
+
AppState.addLog({ level: 'info', message: `Requirements round ${data.round}: ${data.questions.length} questions (${Math.round(data.confidence * 100)}% confidence)` });
|
|
419
|
+
// Questions now handled via chat_message events
|
|
420
|
+
break;
|
|
421
|
+
}
|
|
422
|
+
case 'plan_readiness': {
|
|
423
|
+
if (data.ready) {
|
|
424
|
+
AppState.addLog({ level: 'success', message: `Requirements confirmed (${Math.round(data.confidence * 100)}% confidence): ${data.summary}` });
|
|
425
|
+
} else {
|
|
426
|
+
const gapList = data.gaps.length > 0 ? data.gaps.join(', ') : 'unclear';
|
|
427
|
+
AppState.addLog({ level: 'info', message: `Still gathering requirements (${Math.round(data.confidence * 100)}% confidence). Gaps: ${gapList}` });
|
|
428
|
+
}
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
case 'chat_message': {
|
|
432
|
+
AppState.update({ phase: 'chat' });
|
|
433
|
+
addArchitectMessage(data);
|
|
434
|
+
if (data.readiness) {
|
|
435
|
+
if (data.readiness.ready) {
|
|
436
|
+
AppState.addLog({ level: 'success', message: `Ready to build (${Math.round(data.readiness.confidence * 100)}%): ${data.readiness.summary}` });
|
|
437
|
+
} else if (data.readiness.gaps && data.readiness.gaps.length > 0) {
|
|
438
|
+
AppState.addLog({ level: 'info', message: `Gathering requirements (${Math.round(data.readiness.confidence * 100)}%). Gaps: ${data.readiness.gaps.join(', ')}` });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
case 'plan_summary': {
|
|
444
|
+
// Architecture plan arrived — show summary in chat panel
|
|
445
|
+
AppState.update({ planSummary: data });
|
|
446
|
+
appendActivityItem('\u2692', `Architecture: ${data.featureCount} features, ${data.apiRouteCount} routes, ${data.pageCount} pages`, 'activity-phase');
|
|
447
|
+
AppState.addLog({ level: 'info', message: `Architecture: ${data.featureCount} features, ${data.apiRouteCount} routes, ${data.pageCount} pages` });
|
|
448
|
+
const featureList = (data.features || []).map(f => `${f.name || f.id} (${f.complexity})`).join(', ');
|
|
449
|
+
addArchitectMessage({
|
|
450
|
+
message: `Architecture plan ready: ${data.featureCount} features, ${data.apiRouteCount} routes, ${data.pageCount} pages. ${featureList ? 'Features: ' + featureList + '.' : ''} Auto-approving...`,
|
|
451
|
+
});
|
|
452
|
+
break;
|
|
453
|
+
}
|
|
454
|
+
case 'awaiting_approval': {
|
|
455
|
+
// Pipeline is blocked waiting for approval — auto-approve after showing plan
|
|
456
|
+
AppState.update({ phase: 'requirements_refining' });
|
|
457
|
+
AppState.addLog({ level: 'info', message: 'Plan ready — auto-approving...' });
|
|
458
|
+
// Auto-approve after 4 seconds so audience can see the plan
|
|
459
|
+
setTimeout(() => {
|
|
460
|
+
autoApprovePlan(data.sessionId || window.__appId);
|
|
461
|
+
}, 4000);
|
|
462
|
+
break;
|
|
463
|
+
}
|
|
464
|
+
case 'functional_test': {
|
|
465
|
+
// Functional test progress
|
|
466
|
+
const ftPhase = data.phase || 'functional_testing';
|
|
467
|
+
AppState.update({ phase: ftPhase });
|
|
468
|
+
if (data.passedCount !== undefined) {
|
|
469
|
+
const failed = data.failedCount || 0;
|
|
470
|
+
appendActivityItem('\u2731', `Testing: ${data.passedCount} passed, ${failed} failed`, failed > 0 ? 'activity-warn' : 'activity-success');
|
|
471
|
+
AppState.addLog({ level: 'info', message: `Functional: ${data.passedCount} passed, ${failed} failed` });
|
|
472
|
+
}
|
|
473
|
+
if (data.failureCount) {
|
|
474
|
+
appendActivityItem('\u21bb', `Repairing ${data.failureCount} failures (attempt ${data.attempt})`, 'activity-warn');
|
|
475
|
+
AppState.addLog({ level: 'warn', message: `Repairing ${data.failureCount} test failures (attempt ${data.attempt})` });
|
|
476
|
+
}
|
|
477
|
+
break;
|
|
478
|
+
}
|
|
479
|
+
case 'error': {
|
|
480
|
+
AppState.update({ phase: 'failed' });
|
|
481
|
+
AppState.addLog({ level: 'error', message: data.message || data.error || 'Unknown error' });
|
|
482
|
+
appendActivityItem('\u2718', data.message || data.error || 'Build failed', 'activity-error');
|
|
483
|
+
unlockChat();
|
|
484
|
+
if (!completionShown) {
|
|
485
|
+
showCompletionOverlay({ status: 'failed', appName: 'Application' });
|
|
486
|
+
completionShown = true;
|
|
487
|
+
}
|
|
488
|
+
disconnect();
|
|
489
|
+
break;
|
|
490
|
+
}
|
|
491
|
+
case 'complete': {
|
|
492
|
+
AppState.update({ phase: 'completed' });
|
|
493
|
+
if (data.metrics) {
|
|
494
|
+
AppState.update({ metrics: data.metrics });
|
|
495
|
+
}
|
|
496
|
+
appendActivityItem('\u2713', 'Build complete', 'activity-success');
|
|
497
|
+
unlockChat();
|
|
498
|
+
if (!completionShown) {
|
|
499
|
+
showCompletionOverlay(data);
|
|
500
|
+
completionShown = true;
|
|
501
|
+
}
|
|
502
|
+
disconnect();
|
|
503
|
+
break;
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function updateStatusBar() {
|
|
509
|
+
const state = AppState.get();
|
|
510
|
+
const phaseEl = document.getElementById('status-phase');
|
|
511
|
+
const tasksEl = document.getElementById('status-tasks');
|
|
512
|
+
if (phaseEl) phaseEl.textContent = `phase: ${state.phase}`;
|
|
513
|
+
if (tasksEl) tasksEl.textContent = `tasks: ${state.metrics.tasksComplete}/${state.metrics.totalTasks}`;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function connect(appId) {
|
|
517
|
+
if (eventSource) {
|
|
518
|
+
eventSource.close();
|
|
519
|
+
eventSource = null;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const url = apiKey
|
|
523
|
+
? `${apiBase}/api/v1/app/${appId}/stream?key=${encodeURIComponent(apiKey)}`
|
|
524
|
+
: `${apiBase}/api/v1/app/${appId}/stream`;
|
|
525
|
+
console.log('[SSE] Connecting to:', url);
|
|
526
|
+
eventSource = new EventSource(url);
|
|
527
|
+
|
|
528
|
+
// Listen for named event types
|
|
529
|
+
const eventTypes = [
|
|
530
|
+
'connected', 'progress', 'task_update', 'verification',
|
|
531
|
+
'code_generated', 'agent_status', 'error', 'complete',
|
|
532
|
+
'plan_summary', 'awaiting_approval', 'functional_test',
|
|
533
|
+
'plan_questions',
|
|
534
|
+
'plan_readiness',
|
|
535
|
+
'chat_message'
|
|
536
|
+
];
|
|
537
|
+
|
|
538
|
+
for (const type of eventTypes) {
|
|
539
|
+
eventSource.addEventListener(type, (event) => {
|
|
540
|
+
try {
|
|
541
|
+
const data = JSON.parse(event.data);
|
|
542
|
+
handleSSEEvent(type, data);
|
|
543
|
+
} catch (err) {
|
|
544
|
+
console.error(`[SSE] Failed to parse ${type} event:`, err);
|
|
545
|
+
}
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// Also handle unnamed messages (default event type)
|
|
550
|
+
eventSource.onmessage = (event) => {
|
|
551
|
+
try {
|
|
552
|
+
const data = JSON.parse(event.data);
|
|
553
|
+
// Try to infer the type from the data shape
|
|
554
|
+
if (data.phase) handleSSEEvent('progress', data);
|
|
555
|
+
else if (data.claim) handleSSEEvent('verification', data);
|
|
556
|
+
else if (data.path && data.content) handleSSEEvent('code_generated', data);
|
|
557
|
+
else if (data.error) handleSSEEvent('error', data);
|
|
558
|
+
} catch (err) {
|
|
559
|
+
console.error('[SSE] Failed to parse message:', err);
|
|
560
|
+
}
|
|
561
|
+
};
|
|
562
|
+
|
|
563
|
+
eventSource.onopen = () => {
|
|
564
|
+
console.log('[SSE] Connected successfully');
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
eventSource.onerror = (err) => {
|
|
568
|
+
console.error('[SSE] Connection error:', err);
|
|
569
|
+
if (eventSource.readyState === EventSource.CLOSED) {
|
|
570
|
+
console.warn('[SSE] Connection closed. Check API server and auth key.');
|
|
571
|
+
}
|
|
572
|
+
};
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
function disconnect() {
|
|
576
|
+
if (eventSource) {
|
|
577
|
+
eventSource.close();
|
|
578
|
+
eventSource = null;
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
export function initSSEClient(base, key) {
|
|
583
|
+
apiBase = base || '';
|
|
584
|
+
apiKey = key || '';
|
|
585
|
+
|
|
586
|
+
// Share config with chat module
|
|
587
|
+
configureChat(apiBase, apiKey);
|
|
588
|
+
|
|
589
|
+
// Expose connect for manual invocation from bootstrap
|
|
590
|
+
window.__connectSSE = connect;
|
|
591
|
+
|
|
592
|
+
// Status bar updates
|
|
593
|
+
AppState.on('phase_change', updateStatusBar);
|
|
594
|
+
AppState.on('metric_update', updateStatusBar);
|
|
595
|
+
|
|
596
|
+
// Reset completionShown on new runs
|
|
597
|
+
AppState.on('reset', () => {
|
|
598
|
+
completionShown = false;
|
|
599
|
+
});
|
|
600
|
+
}
|