tryassay 0.22.0 → 0.22.2
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/css/style.css +495 -836
- package/demo/index.html +40 -184
- package/demo/js/chat.js +385 -142
- package/demo/js/preview.js +456 -0
- package/demo/js/sse-client.js +262 -135
- package/demo/js/state.js +11 -1
- package/demo/js/timeline.js +57 -371
- package/dist/api/server.d.ts +2 -0
- package/dist/api/server.js +63 -19
- package/dist/api/server.js.map +1 -1
- package/dist/cli.js +2 -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.js +259 -9
- package/dist/commands/demo.js.map +1 -1
- 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 +432 -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/app-create-orchestrator.d.ts +5 -1
- package/dist/runtime/app-create-orchestrator.js +114 -39
- 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 +88 -0
- package/dist/sdk/forward-verify.js +16 -33
- package/dist/sdk/forward-verify.js.map +1 -1
- package/package.json +1 -1
- package/demo/data/demo-events.json +0 -103
- package/demo/js/demo-mode.js +0 -107
- package/demo/js/orb.js +0 -634
- package/demo/js/question-cards.js +0 -207
- package/demo/js/voice.js +0 -154
package/demo/js/timeline.js
CHANGED
|
@@ -1,394 +1,80 @@
|
|
|
1
|
-
// timeline.js —
|
|
2
|
-
//
|
|
3
|
-
// global elapsed clock, per-phase durations, and heartbeat feedback
|
|
1
|
+
// timeline.js — Compact status bar updates (replaces old timeline panel)
|
|
2
|
+
// Updates the bottom status bar with phase, elapsed time, and task counts.
|
|
4
3
|
|
|
5
|
-
const
|
|
6
|
-
{ id: 'idle', name: 'Idle' },
|
|
7
|
-
{ id: 'initializing', name: 'Initializing' },
|
|
8
|
-
{ id: 'plan_questioning', name: 'Refining Requirements' },
|
|
9
|
-
{ id: 'plan_refining', name: 'Updating Requirements' },
|
|
10
|
-
{ id: 'planning', name: 'Planning Architecture' },
|
|
11
|
-
{ id: 'verifying_plan', name: 'Verifying Plan' },
|
|
12
|
-
{ id: 'requirements_refining', name: 'Reviewing Plan' },
|
|
13
|
-
{ id: 'scaffolding', name: 'Scaffolding Project' },
|
|
14
|
-
{ id: 'building_feature', name: 'Building Features' },
|
|
15
|
-
{ id: 'build_verifying', name: 'Verifying Code' },
|
|
16
|
-
{ id: 'build_repairing', name: 'Repairing Build' },
|
|
17
|
-
{ id: 'integration_verifying', name: 'Integration Verification' },
|
|
18
|
-
{ id: 'functional_testing', name: 'Functional Testing' },
|
|
19
|
-
{ id: 'functional_repairing', name: 'Repairing Failures' },
|
|
20
|
-
{ id: 'cross_verifying', name: 'Cross-Verifying' },
|
|
21
|
-
{ id: 'finalizing', name: 'Finalizing' },
|
|
22
|
-
{ id: 'completed', name: 'Completed' },
|
|
23
|
-
{ id: 'failed', name: 'Failed' }
|
|
24
|
-
];
|
|
4
|
+
const AppState = window.AppState;
|
|
25
5
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
'building_wave': 'building_feature',
|
|
29
|
-
'awaiting_approval': 'requirements_refining',
|
|
30
|
-
};
|
|
6
|
+
let clockInterval = null;
|
|
7
|
+
let startTime = null;
|
|
31
8
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
let globalStartTime = null;
|
|
37
|
-
let globalClockInterval = null;
|
|
38
|
-
let activePhaseClockInterval = null;
|
|
39
|
-
const phaseStartTimes = {}; // phaseId → timestamp
|
|
40
|
-
const phaseDurations = {}; // phaseId → seconds
|
|
41
|
-
let currentActivePhase = 'idle';
|
|
42
|
-
|
|
43
|
-
// ── Heartbeat state ──
|
|
44
|
-
let lastEventTime = null;
|
|
45
|
-
let heartbeatInterval = null;
|
|
46
|
-
const HEARTBEAT_THRESHOLD_MS = 4000; // show heartbeat after 4s of silence
|
|
47
|
-
|
|
48
|
-
function resetPhaseData() {
|
|
49
|
-
for (const p of PHASES) {
|
|
50
|
-
phaseData[p.id] = { tasks: [], verifications: [] };
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function resetTimingState() {
|
|
55
|
-
globalStartTime = null;
|
|
56
|
-
if (globalClockInterval) clearInterval(globalClockInterval);
|
|
57
|
-
if (activePhaseClockInterval) clearInterval(activePhaseClockInterval);
|
|
58
|
-
if (heartbeatInterval) clearInterval(heartbeatInterval);
|
|
59
|
-
globalClockInterval = null;
|
|
60
|
-
activePhaseClockInterval = null;
|
|
61
|
-
heartbeatInterval = null;
|
|
62
|
-
lastEventTime = null;
|
|
63
|
-
currentActivePhase = 'idle';
|
|
64
|
-
for (const key of Object.keys(phaseStartTimes)) delete phaseStartTimes[key];
|
|
65
|
-
for (const key of Object.keys(phaseDurations)) delete phaseDurations[key];
|
|
66
|
-
|
|
67
|
-
// Reset clock display
|
|
68
|
-
const clockEl = document.getElementById('global-clock');
|
|
69
|
-
const clockVal = document.getElementById('clock-value');
|
|
70
|
-
if (clockEl) clockEl.classList.add('hidden');
|
|
71
|
-
if (clockVal) clockVal.textContent = '00:00';
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function resolvePhase(phaseId) {
|
|
75
|
-
return PHASE_ALIASES[phaseId] || phaseId;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function getPhaseIndex(phaseId) {
|
|
79
|
-
const resolved = resolvePhase(phaseId);
|
|
80
|
-
return PHASES.findIndex(p => p.id === resolved);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── Time formatting ──
|
|
84
|
-
|
|
85
|
-
function formatElapsed(ms) {
|
|
86
|
-
const totalSec = Math.floor(ms / 1000);
|
|
87
|
-
const min = Math.floor(totalSec / 60);
|
|
88
|
-
const sec = totalSec % 60;
|
|
89
|
-
const tenths = Math.floor((ms % 1000) / 100);
|
|
90
|
-
if (min > 0) {
|
|
91
|
-
return `${min}:${sec.toString().padStart(2, '0')}.${tenths}`;
|
|
92
|
-
}
|
|
93
|
-
return `${sec}.${tenths}s`;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function formatClockDisplay(ms) {
|
|
97
|
-
const totalSec = Math.floor(ms / 1000);
|
|
98
|
-
const min = Math.floor(totalSec / 60);
|
|
99
|
-
const sec = totalSec % 60;
|
|
100
|
-
return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// ── Global clock ──
|
|
104
|
-
|
|
105
|
-
function startGlobalClock() {
|
|
106
|
-
if (globalStartTime) return; // already running
|
|
107
|
-
globalStartTime = Date.now();
|
|
108
|
-
const clockEl = document.getElementById('global-clock');
|
|
109
|
-
const clockVal = document.getElementById('clock-value');
|
|
110
|
-
if (clockEl) clockEl.classList.remove('hidden');
|
|
111
|
-
|
|
112
|
-
globalClockInterval = setInterval(() => {
|
|
113
|
-
if (!globalStartTime) return;
|
|
114
|
-
const elapsed = Date.now() - globalStartTime;
|
|
115
|
-
if (clockVal) clockVal.textContent = formatClockDisplay(elapsed);
|
|
116
|
-
}, 100);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function stopGlobalClock() {
|
|
120
|
-
// Don't clear globalStartTime — keep final time displayed
|
|
121
|
-
if (globalClockInterval) {
|
|
122
|
-
clearInterval(globalClockInterval);
|
|
123
|
-
globalClockInterval = null;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ── Phase timing ──
|
|
128
|
-
|
|
129
|
-
function onPhaseEnter(phaseId) {
|
|
130
|
-
const resolved = resolvePhase(phaseId);
|
|
131
|
-
|
|
132
|
-
// Start global clock on first non-idle phase
|
|
133
|
-
if (resolved !== 'idle') {
|
|
134
|
-
startGlobalClock();
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
// Record phase start
|
|
138
|
-
phaseStartTimes[resolved] = Date.now();
|
|
139
|
-
currentActivePhase = resolved;
|
|
140
|
-
lastEventTime = Date.now();
|
|
141
|
-
|
|
142
|
-
// Start active phase clock (updates the active step's elapsed badge)
|
|
143
|
-
if (activePhaseClockInterval) clearInterval(activePhaseClockInterval);
|
|
144
|
-
activePhaseClockInterval = setInterval(() => {
|
|
145
|
-
updateActivePhaseElapsed();
|
|
146
|
-
updateHeartbeat();
|
|
147
|
-
}, 100);
|
|
148
|
-
|
|
149
|
-
// Stop global clock on terminal phases
|
|
150
|
-
if (resolved === 'completed' || resolved === 'failed') {
|
|
151
|
-
stopGlobalClock();
|
|
152
|
-
// Record final duration for the terminal phase itself
|
|
153
|
-
phaseDurations[resolved] = 0;
|
|
154
|
-
if (activePhaseClockInterval) {
|
|
155
|
-
clearInterval(activePhaseClockInterval);
|
|
156
|
-
activePhaseClockInterval = null;
|
|
157
|
-
}
|
|
158
|
-
if (heartbeatInterval) {
|
|
159
|
-
clearInterval(heartbeatInterval);
|
|
160
|
-
heartbeatInterval = null;
|
|
9
|
+
export function initTimeline() {
|
|
10
|
+
AppState.on('phase_change', ({ from, to }) => {
|
|
11
|
+
if (from === 'idle' && to !== 'idle') {
|
|
12
|
+
startClock();
|
|
161
13
|
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
function onPhaseExit(phaseId) {
|
|
166
|
-
const resolved = resolvePhase(phaseId);
|
|
167
|
-
const start = phaseStartTimes[resolved];
|
|
168
|
-
if (start) {
|
|
169
|
-
phaseDurations[resolved] = (Date.now() - start) / 1000;
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function updateActivePhaseElapsed() {
|
|
174
|
-
const resolved = currentActivePhase;
|
|
175
|
-
const start = phaseStartTimes[resolved];
|
|
176
|
-
if (!start) return;
|
|
177
|
-
|
|
178
|
-
const elapsedEl = document.querySelector('.phase-elapsed-live');
|
|
179
|
-
if (elapsedEl) {
|
|
180
|
-
const elapsed = Date.now() - start;
|
|
181
|
-
elapsedEl.textContent = formatElapsed(elapsed);
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
// ── Heartbeat ──
|
|
186
|
-
|
|
187
|
-
function recordEvent() {
|
|
188
|
-
lastEventTime = Date.now();
|
|
189
|
-
// Hide heartbeat immediately when event arrives
|
|
190
|
-
const hb = document.querySelector('.heartbeat-indicator');
|
|
191
|
-
if (hb) hb.classList.remove('visible');
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
function updateHeartbeat() {
|
|
195
|
-
if (!lastEventTime) return;
|
|
196
|
-
const silence = Date.now() - lastEventTime;
|
|
197
|
-
const hb = document.querySelector('.heartbeat-indicator');
|
|
198
|
-
if (!hb) return;
|
|
199
|
-
|
|
200
|
-
if (silence > HEARTBEAT_THRESHOLD_MS) {
|
|
201
|
-
hb.classList.add('visible');
|
|
202
|
-
} else {
|
|
203
|
-
hb.classList.remove('visible');
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// ── Render ──
|
|
208
14
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const currentIdx = getPhaseIndex(currentPhase);
|
|
214
|
-
|
|
215
|
-
container.innerHTML = '';
|
|
216
|
-
|
|
217
|
-
for (let i = 0; i < PHASES.length; i++) {
|
|
218
|
-
const phase = PHASES[i];
|
|
219
|
-
const step = document.createElement('div');
|
|
220
|
-
step.className = 'timeline-step';
|
|
221
|
-
step.dataset.phase = phase.id;
|
|
222
|
-
|
|
223
|
-
if (i < currentIdx) {
|
|
224
|
-
step.classList.add('completed');
|
|
225
|
-
} else if (i === currentIdx) {
|
|
226
|
-
step.classList.add('active');
|
|
227
|
-
} else {
|
|
228
|
-
step.classList.add('pending');
|
|
15
|
+
const phaseEl = document.getElementById('status-phase');
|
|
16
|
+
if (phaseEl) {
|
|
17
|
+
phaseEl.textContent = `phase: ${formatPhase(to)}`;
|
|
229
18
|
}
|
|
230
19
|
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const nameEl = document.createElement('div');
|
|
236
|
-
nameEl.className = 'step-name';
|
|
237
|
-
nameEl.textContent = phase.name;
|
|
238
|
-
headerRow.appendChild(nameEl);
|
|
239
|
-
|
|
240
|
-
// Duration badge for completed phases
|
|
241
|
-
if (i < currentIdx && phaseDurations[phase.id] !== undefined) {
|
|
242
|
-
const dur = phaseDurations[phase.id];
|
|
243
|
-
const durEl = document.createElement('span');
|
|
244
|
-
durEl.className = 'phase-duration';
|
|
245
|
-
durEl.textContent = dur < 60 ? `${dur.toFixed(1)}s` : `${Math.floor(dur / 60)}m${Math.floor(dur % 60)}s`;
|
|
246
|
-
headerRow.appendChild(durEl);
|
|
20
|
+
const clockEl = document.getElementById('global-clock');
|
|
21
|
+
if (clockEl && to !== 'idle') {
|
|
22
|
+
clockEl.classList.remove('hidden');
|
|
247
23
|
}
|
|
248
24
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
const elapsedEl = document.createElement('span');
|
|
252
|
-
elapsedEl.className = 'phase-elapsed-live';
|
|
253
|
-
const start = phaseStartTimes[phase.id];
|
|
254
|
-
if (start) {
|
|
255
|
-
elapsedEl.textContent = formatElapsed(Date.now() - start);
|
|
256
|
-
}
|
|
257
|
-
headerRow.appendChild(elapsedEl);
|
|
25
|
+
if (to === 'completed' || to === 'failed') {
|
|
26
|
+
stopClock();
|
|
258
27
|
}
|
|
28
|
+
});
|
|
259
29
|
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const hbEl = document.createElement('div');
|
|
265
|
-
hbEl.className = 'heartbeat-indicator';
|
|
266
|
-
hbEl.innerHTML = '<span class="heartbeat-dot"></span> working';
|
|
267
|
-
step.appendChild(hbEl);
|
|
30
|
+
AppState.on('metric_update', (metrics) => {
|
|
31
|
+
const tasksEl = document.getElementById('status-tasks');
|
|
32
|
+
if (tasksEl) {
|
|
33
|
+
tasksEl.textContent = `tasks: ${metrics.tasksComplete}/${metrics.totalTasks}`;
|
|
268
34
|
}
|
|
35
|
+
});
|
|
269
36
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
const
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const label = document.createTextNode(` ${task.name || task.title || task.id}`);
|
|
283
|
-
taskEl.appendChild(label);
|
|
284
|
-
detailsEl.appendChild(taskEl);
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
for (const v of data.verifications) {
|
|
288
|
-
const vEl = document.createElement('div');
|
|
289
|
-
vEl.className = 'verification-item';
|
|
290
|
-
vEl.classList.add(v.result === 'pass' ? 'pass' : 'fail');
|
|
291
|
-
|
|
292
|
-
const icon = document.createTextNode(v.result === 'pass' ? '\u2713 ' : '\u2717 ');
|
|
293
|
-
vEl.appendChild(icon);
|
|
294
|
-
|
|
295
|
-
const claim = document.createTextNode(v.claim || v.message || 'claim');
|
|
296
|
-
vEl.appendChild(claim);
|
|
37
|
+
AppState.on('reset', () => {
|
|
38
|
+
stopClock();
|
|
39
|
+
const phaseEl = document.getElementById('status-phase');
|
|
40
|
+
const elapsedEl = document.getElementById('status-elapsed');
|
|
41
|
+
const tasksEl = document.getElementById('status-tasks');
|
|
42
|
+
const clockEl = document.getElementById('global-clock');
|
|
43
|
+
if (phaseEl) phaseEl.textContent = 'phase: idle';
|
|
44
|
+
if (elapsedEl) elapsedEl.textContent = 'elapsed: 00:00';
|
|
45
|
+
if (tasksEl) tasksEl.textContent = 'tasks: 0/0';
|
|
46
|
+
if (clockEl) clockEl.classList.add('hidden');
|
|
47
|
+
});
|
|
48
|
+
}
|
|
297
49
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
50
|
+
function startClock() {
|
|
51
|
+
startTime = Date.now();
|
|
52
|
+
clockInterval = setInterval(() => {
|
|
53
|
+
const elapsed = Date.now() - startTime;
|
|
54
|
+
const formatted = formatTime(elapsed);
|
|
302
55
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
}
|
|
56
|
+
const elapsedEl = document.getElementById('status-elapsed');
|
|
57
|
+
if (elapsedEl) elapsedEl.textContent = `elapsed: ${formatted}`;
|
|
306
58
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
}
|
|
59
|
+
const clockValue = document.getElementById('clock-value');
|
|
60
|
+
if (clockValue) clockValue.textContent = formatted;
|
|
61
|
+
}, 1000);
|
|
62
|
+
}
|
|
310
63
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
64
|
+
function stopClock() {
|
|
65
|
+
if (clockInterval) {
|
|
66
|
+
clearInterval(clockInterval);
|
|
67
|
+
clockInterval = null;
|
|
315
68
|
}
|
|
316
69
|
}
|
|
317
70
|
|
|
318
|
-
function
|
|
319
|
-
const
|
|
320
|
-
const
|
|
321
|
-
const
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
if (tasks) tasks.textContent = `${metrics.tasksComplete}/${metrics.totalTasks}`;
|
|
325
|
-
if (claims) claims.textContent = metrics.claimsVerified;
|
|
326
|
-
if (failed) failed.textContent = metrics.claimsFailed;
|
|
327
|
-
if (files) files.textContent = metrics.filesGenerated;
|
|
71
|
+
function formatTime(ms) {
|
|
72
|
+
const totalSeconds = Math.floor(ms / 1000);
|
|
73
|
+
const minutes = Math.floor(totalSeconds / 60);
|
|
74
|
+
const seconds = totalSeconds % 60;
|
|
75
|
+
return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`;
|
|
328
76
|
}
|
|
329
77
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if (!state) return;
|
|
333
|
-
|
|
334
|
-
resetPhaseData();
|
|
335
|
-
resetTimingState();
|
|
336
|
-
renderSteps('idle');
|
|
337
|
-
updateMetrics(state.get().metrics);
|
|
338
|
-
|
|
339
|
-
state.on('phase_change', ({ from, to }) => {
|
|
340
|
-
const resolvedFrom = resolvePhase(from);
|
|
341
|
-
const resolvedTo = resolvePhase(to);
|
|
342
|
-
|
|
343
|
-
// Record duration for outgoing phase
|
|
344
|
-
onPhaseExit(resolvedFrom);
|
|
345
|
-
|
|
346
|
-
// Start timing for incoming phase
|
|
347
|
-
onPhaseEnter(resolvedTo);
|
|
348
|
-
|
|
349
|
-
renderSteps(resolvedTo);
|
|
350
|
-
recordEvent();
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
state.on('task_update', (task) => {
|
|
354
|
-
if (!task) return;
|
|
355
|
-
const currentPhase = resolvePhase(state.get().phase);
|
|
356
|
-
|
|
357
|
-
// Handle single task
|
|
358
|
-
if (task && task.id) {
|
|
359
|
-
const pd = phaseData[currentPhase];
|
|
360
|
-
if (pd && !pd.tasks.find(t => t.id === task.id)) {
|
|
361
|
-
pd.tasks.push(task);
|
|
362
|
-
}
|
|
363
|
-
renderSteps(currentPhase);
|
|
364
|
-
}
|
|
365
|
-
recordEvent();
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
state.on('verification', (v) => {
|
|
369
|
-
if (!v) return;
|
|
370
|
-
const currentPhase = resolvePhase(state.get().phase);
|
|
371
|
-
const pd = phaseData[currentPhase];
|
|
372
|
-
if (pd) {
|
|
373
|
-
pd.verifications.push(v);
|
|
374
|
-
renderSteps(currentPhase);
|
|
375
|
-
}
|
|
376
|
-
recordEvent();
|
|
377
|
-
});
|
|
378
|
-
|
|
379
|
-
state.on('code_generated', () => {
|
|
380
|
-
recordEvent();
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
state.on('metric_update', (metrics) => {
|
|
384
|
-
updateMetrics(metrics);
|
|
385
|
-
recordEvent();
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
state.on('reset', () => {
|
|
389
|
-
resetPhaseData();
|
|
390
|
-
resetTimingState();
|
|
391
|
-
renderSteps('idle');
|
|
392
|
-
updateMetrics({ tasksComplete: 0, totalTasks: 0, claimsVerified: 0, claimsFailed: 0, filesGenerated: 0 });
|
|
393
|
-
});
|
|
78
|
+
function formatPhase(phase) {
|
|
79
|
+
return phase.replace(/_/g, ' ').replace(/\b\w/g, c => c);
|
|
394
80
|
}
|
package/dist/api/server.d.ts
CHANGED
|
@@ -4,8 +4,10 @@ export declare class TeamApiServer {
|
|
|
4
4
|
private appSessions;
|
|
5
5
|
private server;
|
|
6
6
|
private port;
|
|
7
|
+
private demoMode;
|
|
7
8
|
constructor(opts?: {
|
|
8
9
|
port?: number;
|
|
10
|
+
demoMode?: boolean;
|
|
9
11
|
});
|
|
10
12
|
/** Register an API key for testing/development. */
|
|
11
13
|
registerApiKey(key: string, tier: 'team' | 'pro' | 'platform', orgId: string): void;
|
package/dist/api/server.js
CHANGED
|
@@ -11,18 +11,22 @@
|
|
|
11
11
|
// ============================================================
|
|
12
12
|
import { createServer } from 'node:http';
|
|
13
13
|
import { randomUUID } from 'node:crypto';
|
|
14
|
-
import { spawn } from 'node:child_process';
|
|
15
14
|
import { PricingEnforcer } from './pricing-enforcer.js';
|
|
16
15
|
import { TeamSessionManager } from './team-session.js';
|
|
17
16
|
import { AppCreateOrchestrator } from '../runtime/app-create-orchestrator.js';
|
|
17
|
+
import { validateAppDescription } from '../runtime/input-validator.js';
|
|
18
|
+
import { scanForInjection } from '../runtime/prompt-guard.js';
|
|
19
|
+
import { safeSpawn } from '../runtime/safe-executor.js';
|
|
18
20
|
export class TeamApiServer {
|
|
19
21
|
pricingEnforcer;
|
|
20
22
|
sessionManager;
|
|
21
23
|
appSessions = new Map();
|
|
22
24
|
server = null;
|
|
23
25
|
port;
|
|
26
|
+
demoMode;
|
|
24
27
|
constructor(opts) {
|
|
25
28
|
this.port = opts?.port ?? 3800;
|
|
29
|
+
this.demoMode = opts?.demoMode ?? false;
|
|
26
30
|
this.pricingEnforcer = new PricingEnforcer();
|
|
27
31
|
this.sessionManager = new TeamSessionManager();
|
|
28
32
|
}
|
|
@@ -72,15 +76,17 @@ export class TeamApiServer {
|
|
|
72
76
|
res.end();
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
75
|
-
// Authenticate
|
|
79
|
+
// Authenticate (skip in demo mode)
|
|
76
80
|
const apiKey = this.extractApiKey(req);
|
|
77
|
-
if (!
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
this.
|
|
83
|
-
|
|
81
|
+
if (!this.demoMode) {
|
|
82
|
+
if (!apiKey && path !== '/health') {
|
|
83
|
+
this.sendError(res, 401, 'Missing API key. Include Authorization: Bearer <key>');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (apiKey && !this.pricingEnforcer.isValidKey(apiKey) && path !== '/health') {
|
|
87
|
+
this.sendError(res, 403, 'Invalid API key');
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
84
90
|
}
|
|
85
91
|
// Route
|
|
86
92
|
if (path === '/health' && method === 'GET') {
|
|
@@ -283,26 +289,41 @@ export class TeamApiServer {
|
|
|
283
289
|
// ── App Creation Handlers ────────────────────────────────
|
|
284
290
|
async handleAppCreate(req, res, _apiKey) {
|
|
285
291
|
const body = await this.readBody(req);
|
|
286
|
-
let
|
|
292
|
+
let parsed;
|
|
287
293
|
try {
|
|
288
|
-
|
|
294
|
+
parsed = JSON.parse(body);
|
|
289
295
|
}
|
|
290
296
|
catch {
|
|
291
297
|
this.sendError(res, 400, 'Invalid JSON body');
|
|
292
298
|
return;
|
|
293
299
|
}
|
|
294
|
-
|
|
295
|
-
|
|
300
|
+
// Validate and sanitize input at the trust boundary
|
|
301
|
+
const validation = validateAppDescription(parsed);
|
|
302
|
+
if (!validation.valid) {
|
|
303
|
+
this.sendError(res, 400, `Validation: ${validation.errors.map(e => `${e.field}: ${e.message}`).join('; ')}`);
|
|
296
304
|
return;
|
|
297
305
|
}
|
|
306
|
+
const request = validation.sanitized;
|
|
307
|
+
// Scan for prompt injection in user-provided text
|
|
308
|
+
const descGuard = scanForInjection(request.description);
|
|
309
|
+
if (descGuard.action === 'block') {
|
|
310
|
+
this.sendError(res, 400, `Request blocked: suspicious content detected in description (score: ${descGuard.score.toFixed(2)})`);
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
if (descGuard.action === 'warn') {
|
|
314
|
+
console.warn(`[prompt-guard] Warning on app description (score: ${descGuard.score.toFixed(2)}):`, descGuard.findings);
|
|
315
|
+
}
|
|
298
316
|
// Parse optional flags from request body
|
|
299
|
-
const bodyParsed =
|
|
317
|
+
const bodyParsed = parsed;
|
|
300
318
|
const sessionId = randomUUID();
|
|
301
319
|
const orchestrator = new AppCreateOrchestrator(request, {
|
|
302
320
|
outputPath: process.cwd(),
|
|
303
321
|
autoApprove: bodyParsed.autoApprove,
|
|
304
322
|
skipFunctionalTesting: bodyParsed.skipFunctionalTesting,
|
|
305
323
|
skipPlanQuestions: bodyParsed.skipPlanQuestions,
|
|
324
|
+
supabaseOrgId: process.env.SUPABASE_ORG_ID,
|
|
325
|
+
supabaseRegion: process.env.SUPABASE_REGION,
|
|
326
|
+
documents: bodyParsed.documents,
|
|
306
327
|
});
|
|
307
328
|
const session = {
|
|
308
329
|
id: sessionId,
|
|
@@ -563,6 +584,16 @@ export class TeamApiServer {
|
|
|
563
584
|
this.sendError(res, 400, 'Missing required field: answers (array)');
|
|
564
585
|
return;
|
|
565
586
|
}
|
|
587
|
+
// Scan custom text answers for prompt injection
|
|
588
|
+
for (const answer of parsed.answers) {
|
|
589
|
+
if (answer.customText) {
|
|
590
|
+
const answerGuard = scanForInjection(answer.customText);
|
|
591
|
+
if (answerGuard.action === 'block') {
|
|
592
|
+
this.sendError(res, 400, `Answer blocked: suspicious content in custom text (score: ${answerGuard.score.toFixed(2)})`);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
566
597
|
session.orchestrator.submitAnswers(parsed.answers);
|
|
567
598
|
this.sendJson(res, 200, { status: 'received' });
|
|
568
599
|
}
|
|
@@ -589,7 +620,22 @@ export class TeamApiServer {
|
|
|
589
620
|
this.sendError(res, 400, 'Missing required field: message (string)');
|
|
590
621
|
return;
|
|
591
622
|
}
|
|
592
|
-
|
|
623
|
+
// Scan chat message for prompt injection
|
|
624
|
+
const chatGuard = scanForInjection(parsed.message);
|
|
625
|
+
if (chatGuard.action === 'block') {
|
|
626
|
+
this.sendError(res, 400, `Message blocked: suspicious content detected (score: ${chatGuard.score.toFixed(2)})`);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (chatGuard.action === 'warn') {
|
|
630
|
+
console.warn(`[prompt-guard] Warning on chat message (score: ${chatGuard.score.toFixed(2)}):`, chatGuard.findings);
|
|
631
|
+
}
|
|
632
|
+
// Append document contents to message if provided
|
|
633
|
+
let messageWithDocs = parsed.message;
|
|
634
|
+
if (parsed.documents && parsed.documents.length > 0) {
|
|
635
|
+
const docText = parsed.documents.map(d => `\n\n--- Attached: ${d.name} ---\n${d.content}`).join('');
|
|
636
|
+
messageWithDocs = parsed.message + docText;
|
|
637
|
+
}
|
|
638
|
+
session.orchestrator.submitChatMessage(messageWithDocs, parsed.cardAnswers);
|
|
593
639
|
this.sendJson(res, 200, { status: 'received' });
|
|
594
640
|
}
|
|
595
641
|
// ── App Launch Handlers ─────────────────────────────────
|
|
@@ -613,9 +659,8 @@ export class TeamApiServer {
|
|
|
613
659
|
}
|
|
614
660
|
const projectPath = session.result.projectPath;
|
|
615
661
|
session.launchStatus = 'installing';
|
|
616
|
-
const install =
|
|
662
|
+
const install = safeSpawn('npm', ['install'], {
|
|
617
663
|
cwd: projectPath,
|
|
618
|
-
shell: true,
|
|
619
664
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
620
665
|
});
|
|
621
666
|
install.on('close', (code) => {
|
|
@@ -625,9 +670,8 @@ export class TeamApiServer {
|
|
|
625
670
|
return;
|
|
626
671
|
}
|
|
627
672
|
session.launchStatus = 'starting';
|
|
628
|
-
const dev =
|
|
673
|
+
const dev = safeSpawn('npm', ['run', 'dev'], {
|
|
629
674
|
cwd: projectPath,
|
|
630
|
-
shell: true,
|
|
631
675
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
632
676
|
});
|
|
633
677
|
session.launchProcess = dev;
|