ft-scout 9.0.1 → 9.0.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.
Files changed (48) hide show
  1. package/.firebase/hosting.d2Vi.cache +7 -7
  2. package/README.md +93 -13
  3. package/bin/src/commands/agent.d.ts.map +1 -1
  4. package/bin/src/commands/agent.js +46 -3
  5. package/bin/src/commands/agent.js.map +1 -1
  6. package/bin/src/engine/actionVerifier.d.ts.map +1 -0
  7. package/bin/src/engine/actionVerifier.js +223 -0
  8. package/bin/src/engine/actionVerifier.js.map +1 -0
  9. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  10. package/bin/src/engine/agentEngine.js +233 -1
  11. package/bin/src/engine/agentEngine.js.map +1 -1
  12. package/bin/src/engine/agentState.d.ts.map +1 -0
  13. package/bin/src/engine/agentState.js +445 -0
  14. package/bin/src/engine/agentState.js.map +1 -0
  15. package/bin/src/engine/appControl.d.ts.map +1 -1
  16. package/bin/src/engine/appControl.js +19 -6
  17. package/bin/src/engine/appControl.js.map +1 -1
  18. package/bin/src/engine/browser/browserController.d.ts.map +1 -0
  19. package/bin/src/engine/browser/browserController.js +689 -0
  20. package/bin/src/engine/browser/browserController.js.map +1 -0
  21. package/bin/src/engine/browser/index.d.ts.map +1 -0
  22. package/bin/src/engine/browser/index.js +2 -0
  23. package/bin/src/engine/browser/index.js.map +1 -0
  24. package/bin/src/engine/computer/computerController.d.ts.map +1 -0
  25. package/bin/src/engine/computer/computerController.js +295 -0
  26. package/bin/src/engine/computer/computerController.js.map +1 -0
  27. package/bin/src/engine/computer/index.d.ts.map +1 -0
  28. package/bin/src/engine/computer/index.js +2 -0
  29. package/bin/src/engine/computer/index.js.map +1 -0
  30. package/bin/src/engine/planner.d.ts.map +1 -0
  31. package/bin/src/engine/planner.js +242 -0
  32. package/bin/src/engine/planner.js.map +1 -0
  33. package/bin/src/engine/safety.d.ts.map +1 -0
  34. package/bin/src/engine/safety.js +281 -0
  35. package/bin/src/engine/safety.js.map +1 -0
  36. package/bin/src/engine/toolRegistry.d.ts.map +1 -0
  37. package/bin/src/engine/toolRegistry.js +218 -0
  38. package/bin/src/engine/toolRegistry.js.map +1 -0
  39. package/bin/src/index.js +6 -4
  40. package/bin/src/index.js.map +1 -1
  41. package/bin/src/server/server.d.ts.map +1 -1
  42. package/bin/src/server/server.js +116 -0
  43. package/bin/src/server/server.js.map +1 -1
  44. package/bin/src/utils/branding.js +1 -1
  45. package/downloads/INVOICE_2026_ACME.pdf +1 -0
  46. package/package.json +1 -1
  47. package/web/app.js +277 -46
  48. package/web/styles.css +79 -2
package/web/app.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /* ==========================================================================
2
- FrontTerrain Scout — Multi-Page Application Engine v9.0.1
2
+ FrontTerrain Scout — Multi-Page Application Engine v9.0.2
3
3
  ========================================================================== */
4
4
 
5
5
  const FIREBASE_CONFIG = {
@@ -185,14 +185,35 @@ async function checkPaymentRedirectCallback() {
185
185
  }
186
186
 
187
187
  // Dynamic Pricing Loader & Admin Navigation Hook
188
+ function loadPricingFromStorage() {
189
+ try {
190
+ const stored = localStorage.getItem('scout_pricing_config');
191
+ if (stored) {
192
+ const parsed = JSON.parse(stored);
193
+ if (parsed && typeof parsed === 'object' && parsed.pro) {
194
+ state.pricing = { ...state.pricing, ...parsed };
195
+ }
196
+ }
197
+ } catch { }
198
+ }
199
+
188
200
  async function loadDynamicPricing() {
201
+ // 1. Instantly apply locally saved admin pricing configuration
202
+ loadPricingFromStorage();
203
+ renderTiersUI();
204
+
205
+ // 2. Fetch fresh server pricing if connected
189
206
  try {
190
207
  const res = await fetch('/api/pricing');
191
208
  if (res.ok) {
192
- const data = await res.json();
193
- if (data && data.success && data.pricing) {
194
- state.pricing = data.pricing;
195
- renderTiersUI();
209
+ const contentType = res.headers.get('content-type') || '';
210
+ if (contentType.includes('application/json')) {
211
+ const data = await res.json();
212
+ if (data && data.success && data.pricing) {
213
+ state.pricing = data.pricing;
214
+ localStorage.setItem('scout_pricing_config', JSON.stringify(data.pricing));
215
+ renderTiersUI();
216
+ }
196
217
  }
197
218
  }
198
219
  } catch { }
@@ -240,9 +261,18 @@ document.addEventListener('DOMContentLoaded', async () => {
240
261
  await loadDynamicPricing();
241
262
  await checkPaymentRedirectCallback();
242
263
 
264
+ window.addEventListener('storage', (e) => {
265
+ if (e.key === 'scout_pricing_config') {
266
+ loadPricingFromStorage();
267
+ renderTiersUI();
268
+ }
269
+ });
270
+
243
271
  if (getEl('pacman-canvas')) {
244
272
  initPacmanGame();
245
273
  }
274
+
275
+ initAutonomousAgentMonitor();
246
276
  });
247
277
 
248
278
  // Time Helper Function for Dynamic Relative Timestamps
@@ -350,6 +380,7 @@ function loadTierAndCreditsFromStorage() {
350
380
  state.customApiKey = localStorage.getItem('scout_byok_key') || '';
351
381
  state.customBaseUrl = localStorage.getItem('scout_byok_endpoint') || '';
352
382
 
383
+ loadPricingFromStorage();
353
384
  checkSubscriptionExpiration();
354
385
  }
355
386
 
@@ -376,14 +407,14 @@ function openCheckoutModal(targetTier) {
376
407
  let quota = '100 Credits / mo';
377
408
 
378
409
  if (targetTier === 'pro') {
379
- const isOffer = p.pro?.offerActive && !isAnnual && p.pro?.offerPriceINR !== undefined;
380
- const inr = isOffer ? p.pro.offerPriceINR : (isAnnual ? (p.pro?.annualPriceINR ?? 45) : (p.pro?.monthlyPriceINR ?? 50));
381
- price = inr.toLocaleString('en-IN');
410
+ const isOffer = p.pro?.offerActive && !isAnnual && p.pro?.offerPriceUSD !== undefined;
411
+ const usd = isOffer ? p.pro.offerPriceUSD : (isAnnual ? (p.pro?.annualPriceUSD ?? 0.79) : (p.pro?.monthlyPriceUSD ?? 0.99));
412
+ price = usd.toString();
382
413
  quota = `${(p.pro?.credits || 1000).toLocaleString()} Credits / mo`;
383
414
  } else if (targetTier === 'enterprise') {
384
- const isOffer = p.enterprise?.offerActive && !isAnnual && p.enterprise?.offerPriceINR !== undefined;
385
- const inr = isOffer ? p.enterprise.offerPriceINR : (isAnnual ? (p.enterprise?.annualPriceINR ?? 37990) : (p.enterprise?.monthlyPriceINR ?? 3999));
386
- price = inr.toLocaleString('en-IN');
415
+ const isOffer = p.enterprise?.offerActive && !isAnnual && p.enterprise?.offerPriceUSD !== undefined;
416
+ const usd = isOffer ? p.enterprise.offerPriceUSD : (isAnnual ? (p.enterprise?.annualPriceUSD ?? 39) : (p.enterprise?.monthlyPriceUSD ?? 49));
417
+ price = usd.toString();
387
418
  quota = `${(p.enterprise?.credits || 10000).toLocaleString()} Credits / mo`;
388
419
  }
389
420
 
@@ -391,7 +422,7 @@ function openCheckoutModal(targetTier) {
391
422
  if (getEl('checkout-billing-cycle')) getEl('checkout-billing-cycle').innerText = isAnnual ? 'Billed Annually (Save more)' : 'Billed Monthly';
392
423
  if (getEl('checkout-plan-price')) getEl('checkout-plan-price').innerText = price;
393
424
  const checkoutCurrency = document.querySelector('.checkout-price-display .price-currency');
394
- if (checkoutCurrency) checkoutCurrency.innerText = '₹';
425
+ if (checkoutCurrency) checkoutCurrency.innerText = '$';
395
426
  if (getEl('checkout-price-period')) getEl('checkout-price-period').innerText = isAnnual ? '/ mo (annual)' : '/ mo';
396
427
  if (getEl('checkout-quota-val')) getEl('checkout-quota-val').innerText = quota;
397
428
 
@@ -450,37 +481,37 @@ function confirmScheduleDowngrade() {
450
481
 
451
482
  function confirmImmediateDowngrade() {
452
483
  const targetTier = pendingDowngradeTier || 'free';
484
+ const previousPaidTier = state.paidTier || (state.currentTier !== 'free' ? state.currentTier : null);
485
+
453
486
  state.currentTier = targetTier;
454
- state.paidTier = targetTier === 'free' ? null : targetTier;
455
- state.cancelAtPeriodEnd = false;
456
- state.pendingDowngrade = null;
487
+ if (previousPaidTier && !isSubscriptionExpired()) {
488
+ state.paidTier = previousPaidTier;
489
+ } else {
490
+ state.paidTier = targetTier === 'free' ? null : targetTier;
491
+ }
492
+ state.cancelAtPeriodEnd = true;
493
+ state.pendingDowngrade = targetTier;
457
494
  state.creditsTotal = targetTier === 'pro' ? 1000 : (targetTier === 'enterprise' ? 10000 : 100);
458
- state.creditsUsed = 0;
459
- const now = new Date();
460
- state.renewAt = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 0, 0).toISOString();
495
+
461
496
  saveSessionToStorage();
462
497
  if (state.serverConnected) syncAuthWithServer();
463
498
  closeDowngradeModal();
464
499
  renderAllDynamicComponents();
465
- showToast(`Downgraded to ${TIER_DETAILS[targetTier].name} immediately.`, 'info');
500
+ showToast(`Switched to ${TIER_DETAILS[targetTier].name}. You can resume ${TIER_DETAILS[previousPaidTier || 'pro']?.name || 'your plan'} anytime before ${formatRenewDate(state.renewAt)} without paying again!`, 'info');
466
501
  }
467
502
 
468
503
  function resumeSubscription() {
469
- if (!state.currentTier || state.currentTier === 'free') {
470
- if (state.paidTier && state.paidTier !== 'free' && !isSubscriptionExpired()) {
471
- state.currentTier = state.paidTier;
472
- state.creditsTotal = state.currentTier === 'pro' ? 1000 : 10000;
473
- } else {
474
- return;
475
- }
476
- }
504
+ const resumeTier = (state.paidTier && state.paidTier !== 'free' && !isSubscriptionExpired()) ? state.paidTier : (state.currentTier !== 'free' ? state.currentTier : 'pro');
505
+ state.currentTier = resumeTier;
506
+ state.paidTier = resumeTier;
477
507
  state.cancelAtPeriodEnd = false;
478
508
  state.pendingDowngrade = null;
479
- state.paidTier = state.currentTier;
509
+ state.creditsTotal = resumeTier === 'pro' ? 1000 : (resumeTier === 'enterprise' ? 10000 : 100);
510
+
480
511
  saveSessionToStorage();
481
512
  if (state.serverConnected) syncAuthWithServer();
482
513
  renderAllDynamicComponents();
483
- showToast(`🎉 Subscription resumed! Your ${TIER_DETAILS[state.currentTier].name} is active and will auto-renew.`, 'success');
514
+ showToast(`🎉 Subscription resumed! Your ${TIER_DETAILS[resumeTier].name} is active again with no extra charge.`, 'success');
484
515
  }
485
516
 
486
517
  // Render and initialize Razorpay button UI
@@ -1050,7 +1081,7 @@ function initTerminalSimulator() {
1050
1081
  `;
1051
1082
  } else if (lower.includes('status')) {
1052
1083
  response.innerHTML = `
1053
- <span class="term-green">✔ Scout CLI Engine: v9.0.1 Online</span><br>
1084
+ <span class="term-green">✔ Scout CLI Engine: v9.0.2 Online</span><br>
1054
1085
  ↳ User: <strong>${escapeHtml(state.user ? state.user.name : 'Guest Session')}</strong><br>
1055
1086
  ↳ Active Subscription: <strong>${escapeHtml(TIER_DETAILS[state.currentTier].name)}</strong><br>
1056
1087
  ↳ Credit Quota: <strong>${state.byokEnabled ? 'BYOK Mode (Unlimited)' : `${state.creditsUsed} / ${state.creditsTotal} Credits Used`}</strong><br>
@@ -1295,33 +1326,33 @@ function renderTiersUI() {
1295
1326
 
1296
1327
  const isAnnual = state.billingCycle === 'annual';
1297
1328
  const p = state.pricing || {};
1298
- const isProOffer = p.pro?.offerActive && !isAnnual && p.pro?.offerPriceINR !== undefined;
1299
- const isEntOffer = p.enterprise?.offerActive && !isAnnual && p.enterprise?.offerPriceINR !== undefined;
1329
+ const isProOffer = p.pro?.offerActive && !isAnnual && p.pro?.offerPriceUSD !== undefined;
1330
+ const isEntOffer = p.enterprise?.offerActive && !isAnnual && p.enterprise?.offerPriceUSD !== undefined;
1300
1331
 
1301
- const proINR = isProOffer ? p.pro.offerPriceINR : (isAnnual ? (p.pro?.annualPriceINR ?? 45) : (p.pro?.monthlyPriceINR ?? 50));
1302
- const entINR = isEntOffer ? p.enterprise.offerPriceINR : (isAnnual ? (p.enterprise?.annualPriceINR ?? 37990) : (p.enterprise?.monthlyPriceINR ?? 3999));
1332
+ const proUSD = isProOffer ? p.pro.offerPriceUSD : (isAnnual ? (p.pro?.annualPriceUSD ?? 0.79) : (p.pro?.monthlyPriceUSD ?? 0.99));
1333
+ const entUSD = isEntOffer ? p.enterprise.offerPriceUSD : (isAnnual ? (p.enterprise?.annualPriceUSD ?? 39) : (p.enterprise?.monthlyPriceUSD ?? 49));
1303
1334
 
1304
1335
  if (getEl('price-pro-val')) {
1305
- if (isProOffer && p.pro?.monthlyPriceINR) {
1306
- getEl('price-pro-val').innerHTML = `<s style="font-size: 0.55em; opacity: 0.55; margin-right: 6px;">₹${p.pro.monthlyPriceINR}</s>₹${proINR}`;
1336
+ if (isProOffer && p.pro?.monthlyPriceUSD) {
1337
+ getEl('price-pro-val').innerHTML = `<s style="font-size: 0.55em; opacity: 0.55; margin-right: 6px;">$${p.pro.monthlyPriceUSD}</s>$${proUSD}`;
1307
1338
  } else {
1308
- getEl('price-pro-val').innerText = proINR;
1339
+ getEl('price-pro-val').innerText = proUSD;
1309
1340
  }
1310
1341
  }
1311
- if (getEl('price-pro-period')) getEl('price-pro-period').innerText = isProOffer ? `/ month (${p.pro?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '/ month (billed annually)' : '/ month');
1342
+ if (getEl('price-pro-period')) getEl('price-pro-period').innerText = isProOffer ? `/ month (${p.pro?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '(billed annually)' : '/ month');
1312
1343
  const proCurrency = document.querySelector('#tier-card-pro .price-currency');
1313
- if (proCurrency) proCurrency.innerText = isProOffer ? '' : '₹';
1344
+ if (proCurrency) proCurrency.innerText = isProOffer ? '' : '$';
1314
1345
 
1315
1346
  if (getEl('price-ent-val')) {
1316
- if (isEntOffer && p.enterprise?.monthlyPriceINR) {
1317
- getEl('price-ent-val').innerHTML = `<s style="font-size: 0.55em; opacity: 0.55; margin-right: 6px;">₹${p.enterprise.monthlyPriceINR.toLocaleString('en-IN')}</s>₹${entINR.toLocaleString('en-IN')}`;
1347
+ if (isEntOffer && p.enterprise?.monthlyPriceUSD) {
1348
+ getEl('price-ent-val').innerHTML = `<s style="font-size: 0.55em; opacity: 0.55; margin-right: 6px;">$${p.enterprise.monthlyPriceUSD}</s>$${entUSD}`;
1318
1349
  } else {
1319
- getEl('price-ent-val').innerText = entINR.toLocaleString('en-IN');
1350
+ getEl('price-ent-val').innerText = entUSD;
1320
1351
  }
1321
1352
  }
1322
- if (getEl('price-ent-period')) getEl('price-ent-period').innerText = isEntOffer ? `/ month (${p.enterprise?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '/ month (billed annually)' : '/ month');
1353
+ if (getEl('price-ent-period')) getEl('price-ent-period').innerText = isEntOffer ? `/ month (${p.enterprise?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '(billed annually)' : '/ month');
1323
1354
  const entCurrency = document.querySelector('#tier-card-enterprise .price-currency');
1324
- if (entCurrency) entCurrency.innerText = isEntOffer ? '' : '₹';
1355
+ if (entCurrency) entCurrency.innerText = isEntOffer ? '' : '$';
1325
1356
 
1326
1357
  ['free', 'pro', 'enterprise'].forEach((t) => {
1327
1358
  const card = getEl(`tier-card-${t}`);
@@ -1354,7 +1385,7 @@ function renderTiersUI() {
1354
1385
  } else if (t === 'pro') {
1355
1386
  if (state.paidTier === 'pro' && !isSubscriptionExpired()) {
1356
1387
  btn.className = 'btn btn-primary btn-block btn-select-tier';
1357
- btn.innerHTML = `<i class="fa-solid fa-rotate-left"></i> Reactivate Pro (Active until ${formattedDate})`;
1388
+ btn.innerHTML = `<i class="fa-solid fa-rotate-left"></i> Resume Pro Plan (No Extra Charge)`;
1358
1389
  btn.disabled = false;
1359
1390
  } else if (state.currentTier === 'enterprise') {
1360
1391
  if (isScheduledCancel && state.pendingDowngrade === 'pro') {
@@ -1374,7 +1405,7 @@ function renderTiersUI() {
1374
1405
  } else if (t === 'enterprise') {
1375
1406
  if (state.paidTier === 'enterprise' && !isSubscriptionExpired()) {
1376
1407
  btn.className = 'btn btn-warning btn-block btn-select-tier';
1377
- btn.innerHTML = `<i class="fa-solid fa-rotate-left"></i> Reactivate Enterprise (Active until ${formattedDate})`;
1408
+ btn.innerHTML = `<i class="fa-solid fa-rotate-left"></i> Resume Enterprise (No Extra Charge)`;
1378
1409
  btn.disabled = false;
1379
1410
  } else {
1380
1411
  btn.className = 'btn btn-outline btn-block btn-select-tier';
@@ -2301,3 +2332,203 @@ function initPacmanGame() {
2301
2332
 
2302
2333
  render();
2303
2334
  }
2335
+
2336
+ /* ==========================================================================
2337
+ Autonomous Agent Execution Monitor & Human-in-the-Loop Controller
2338
+ ========================================================================== */
2339
+ let agentMonitorPollTimer = null;
2340
+ let currentPendingApprovalId = null;
2341
+
2342
+ function initAutonomousAgentMonitor() {
2343
+ const monitorPanel = getEl('agent-monitor-panel');
2344
+ if (!monitorPanel) return;
2345
+
2346
+ const taskForm = getEl('agent-task-form');
2347
+ const goalInput = getEl('agent-goal-input');
2348
+ const autoApproveToggle = getEl('agent-auto-approve-toggle');
2349
+ const btnRefresh = getEl('btn-refresh-agent-state');
2350
+ const btnApprove = getEl('btn-approve-action');
2351
+ const btnDeny = getEl('btn-deny-action');
2352
+
2353
+ async function fetchAgentState() {
2354
+ try {
2355
+ const apiBase = state.apiBaseUrl || '';
2356
+ const res = await fetch(`${apiBase}/api/agent/state`);
2357
+ if (!res.ok) return;
2358
+ const data = await res.json();
2359
+ if (!data.success) return;
2360
+
2361
+ renderAgentState(data.state, data.pendingApprovals, data.scratchpad);
2362
+ } catch (err) {
2363
+ console.warn('Agent state poll error:', err);
2364
+ }
2365
+ }
2366
+
2367
+ function renderAgentState(taskState, pendingApprovals, scratchpad) {
2368
+ if (!taskState) return;
2369
+
2370
+ // Status badge
2371
+ const badge = getEl('agent-status-badge');
2372
+ if (badge) {
2373
+ const st = (taskState.status || 'idle').toUpperCase();
2374
+ badge.innerText = st;
2375
+ badge.className = 'badge';
2376
+ if (st === 'COMPLETED') badge.classList.add('badge-success');
2377
+ else if (st === 'EXECUTING' || st === 'PLANNING') badge.classList.add('badge-accent');
2378
+ else if (st === 'WAITING_APPROVAL') badge.classList.add('badge-warning');
2379
+ else if (st === 'FAILED') badge.classList.add('badge-danger');
2380
+ else badge.classList.add('badge-outline');
2381
+ }
2382
+
2383
+ // Step counter
2384
+ const stepCounter = getEl('agent-step-counter');
2385
+ if (stepCounter) {
2386
+ stepCounter.innerText = `Step ${taskState.currentStep || 0} / ${taskState.maxSteps || 30}`;
2387
+ }
2388
+
2389
+ // Plan progress & list
2390
+ const planList = getEl('agent-plan-list');
2391
+ const planText = getEl('agent-plan-progress-text');
2392
+ if (planList && taskState.plan && taskState.plan.length > 0) {
2393
+ const completedCount = taskState.plan.filter(p => p.status === 'completed').length;
2394
+ if (planText) planText.innerText = `${completedCount} / ${taskState.plan.length} steps completed`;
2395
+
2396
+ planList.innerHTML = taskState.plan.map(step => {
2397
+ let icon = '<i class="fa-regular fa-circle text-tertiary"></i>';
2398
+ if (step.status === 'completed') icon = '<i class="fa-solid fa-circle-check text-success"></i>';
2399
+ else if (step.status === 'in_progress') icon = '<i class="fa-solid fa-spinner fa-spin text-primary"></i>';
2400
+ else if (step.status === 'failed') icon = '<i class="fa-solid fa-circle-xmark text-danger"></i>';
2401
+
2402
+ return `
2403
+ <div class="agent-plan-step-item step-${step.status}">
2404
+ ${icon}
2405
+ <span style="font-weight: 500;">Step ${step.id}: ${escapeHtml(step.description)}</span>
2406
+ ${step.toolName ? `<span class="badge" style="font-size: 10px; margin-left: auto;">${escapeHtml(step.toolName)}</span>` : ''}
2407
+ </div>
2408
+ `;
2409
+ }).join('');
2410
+ }
2411
+
2412
+ // Pending Approvals Banner
2413
+ const banner = getEl('agent-approval-banner');
2414
+ if (banner) {
2415
+ if (pendingApprovals && pendingApprovals.length > 0) {
2416
+ const req = pendingApprovals[0];
2417
+ currentPendingApprovalId = req.id;
2418
+ banner.classList.remove('hidden');
2419
+ const riskBadge = getEl('approval-risk-badge');
2420
+ if (riskBadge) {
2421
+ riskBadge.innerText = `⚠️ ${req.riskLevel.toUpperCase()} RISK ACTION AWAITING APPROVAL`;
2422
+ riskBadge.style.background = req.riskLevel === 'CRITICAL' ? '#dc2626' : '#ef4444';
2423
+ }
2424
+ const actTitle = getEl('approval-action-title');
2425
+ if (actTitle) actTitle.innerText = `Action: ${req.action} on ${req.target || 'system'}`;
2426
+ const actDesc = getEl('approval-action-desc');
2427
+ if (actDesc) actDesc.innerText = req.description;
2428
+ } else {
2429
+ banner.classList.add('hidden');
2430
+ currentPendingApprovalId = null;
2431
+ }
2432
+ }
2433
+
2434
+ // Scratchpad view
2435
+ const scratchpadView = getEl('agent-scratchpad-view');
2436
+ if (scratchpadView && scratchpad) {
2437
+ scratchpadView.innerText = scratchpad;
2438
+ }
2439
+
2440
+ // Artifacts list
2441
+ const artList = getEl('agent-artifacts-list');
2442
+ if (artList) {
2443
+ if (taskState.artifacts && taskState.artifacts.length > 0) {
2444
+ artList.innerHTML = taskState.artifacts.map(a => `
2445
+ <div style="display: flex; align-items: center; justify-content: space-between; padding: 4px 8px; background: rgba(255,255,255,0.04); border-radius: 4px;">
2446
+ <span><i class="fa-solid fa-file-code text-accent"></i> <strong>${escapeHtml(a.name)}</strong></span>
2447
+ <span style="color: var(--text-tertiary); font-size: 11px;">${escapeHtml(a.type)}</span>
2448
+ </div>
2449
+ `).join('');
2450
+ } else {
2451
+ artList.innerHTML = '<span style="color: var(--text-tertiary);">No artifacts produced yet.</span>';
2452
+ }
2453
+ }
2454
+ }
2455
+
2456
+ taskForm?.addEventListener('submit', async (e) => {
2457
+ e.preventDefault();
2458
+ const goal = goalInput?.value?.trim();
2459
+ if (!goal) return;
2460
+ const autoApprove = autoApproveToggle?.checked || false;
2461
+
2462
+ const btn = getEl('btn-start-agent');
2463
+ if (btn) {
2464
+ btn.disabled = true;
2465
+ btn.innerHTML = '<i class="fa-solid fa-spinner fa-spin"></i> Initializing...';
2466
+ }
2467
+
2468
+ try {
2469
+ const apiBase = state.apiBaseUrl || '';
2470
+ const res = await fetch(`${apiBase}/api/agent/task`, {
2471
+ method: 'POST',
2472
+ headers: { 'Content-Type': 'application/json' },
2473
+ body: JSON.stringify({ goal, autoApprove }),
2474
+ });
2475
+ const data = await res.json();
2476
+ if (data.success) {
2477
+ showToast(`Autonomous Agent started! Task ID: ${data.taskId.slice(0, 8)}...`, 'success');
2478
+ setTimeout(fetchAgentState, 600);
2479
+ } else {
2480
+ showToast(data.error || 'Failed to start agent task', 'error');
2481
+ }
2482
+ } catch (err) {
2483
+ showToast('Failed to connect to agent server', 'error');
2484
+ } finally {
2485
+ if (btn) {
2486
+ btn.disabled = false;
2487
+ btn.innerHTML = '<i class="fa-solid fa-play"></i> Run Agent';
2488
+ }
2489
+ }
2490
+ });
2491
+
2492
+ btnRefresh?.addEventListener('click', () => {
2493
+ fetchAgentState();
2494
+ showToast('Agent state refreshed', 'info');
2495
+ });
2496
+
2497
+ btnApprove?.addEventListener('click', async () => {
2498
+ if (!currentPendingApprovalId) return;
2499
+ try {
2500
+ const apiBase = state.apiBaseUrl || '';
2501
+ await fetch(`${apiBase}/api/agent/approval`, {
2502
+ method: 'POST',
2503
+ headers: { 'Content-Type': 'application/json' },
2504
+ body: JSON.stringify({ requestId: currentPendingApprovalId, approved: true }),
2505
+ });
2506
+ showToast('Action approved! Agent resuming execution...', 'success');
2507
+ fetchAgentState();
2508
+ } catch {
2509
+ showToast('Failed to send approval', 'error');
2510
+ }
2511
+ });
2512
+
2513
+ btnDeny?.addEventListener('click', async () => {
2514
+ if (!currentPendingApprovalId) return;
2515
+ try {
2516
+ const apiBase = state.apiBaseUrl || '';
2517
+ await fetch(`${apiBase}/api/agent/approval`, {
2518
+ method: 'POST',
2519
+ headers: { 'Content-Type': 'application/json' },
2520
+ body: JSON.stringify({ requestId: currentPendingApprovalId, approved: false, reason: 'Denied by user from Dashboard Web UI' }),
2521
+ });
2522
+ showToast('Action denied. Agent informed to seek alternate path.', 'warning');
2523
+ fetchAgentState();
2524
+ } catch {
2525
+ showToast('Failed to send denial', 'error');
2526
+ }
2527
+ });
2528
+
2529
+ // Initial fetch & polling every 2.5s
2530
+ fetchAgentState();
2531
+ if (agentMonitorPollTimer) clearInterval(agentMonitorPollTimer);
2532
+ agentMonitorPollTimer = setInterval(fetchAgentState, 2500);
2533
+ }
2534
+
package/web/styles.css CHANGED
@@ -1,5 +1,5 @@
1
1
  /* ==========================================================================
2
- FrontTerrain Scout — Design System & Visual Engine v9.0.1
2
+ FrontTerrain Scout — Design System & Visual Engine v9.0.2
3
3
  Inspired by HappyRobot, Linear.app & Vercel aesthetics
4
4
  ========================================================================== */
5
5
 
@@ -1063,7 +1063,7 @@ h1, h2, h3, h4, h5, h6 {
1063
1063
  .footer-links a:hover { color: var(--hr-rust); }
1064
1064
 
1065
1065
  /* ==========================================================================
1066
- Visual Perception & Live Screen Sharing Engine v9.0.1
1066
+ Visual Perception & Live Screen Sharing Engine v9.0.2
1067
1067
  ========================================================================== */
1068
1068
  .vision-banner {
1069
1069
  margin-bottom: 28px;
@@ -1543,3 +1543,80 @@ h1, h2, h3, h4, h5, h6 {
1543
1543
  font-size: 12px;
1544
1544
  }
1545
1545
  }
1546
+
1547
+ /* ==========================================================================
1548
+ Autonomous Agent Execution Monitor & Human-in-the-Loop Styles
1549
+ ========================================================================== */
1550
+ .agent-monitor-card {
1551
+ border: 1px solid rgba(0, 229, 255, 0.2);
1552
+ position: relative;
1553
+ overflow: hidden;
1554
+ }
1555
+
1556
+ .agent-monitor-card::before {
1557
+ content: '';
1558
+ position: absolute;
1559
+ top: 0;
1560
+ left: 0;
1561
+ right: 0;
1562
+ height: 2px;
1563
+ background: linear-gradient(90deg, #38bdf8, #818cf8, #e15b37);
1564
+ opacity: 0.8;
1565
+ }
1566
+
1567
+ .agent-pulse-icon {
1568
+ width: 36px;
1569
+ height: 36px;
1570
+ border-radius: 8px;
1571
+ background: rgba(0, 229, 255, 0.12);
1572
+ border: 1px solid rgba(0, 229, 255, 0.3);
1573
+ display: flex;
1574
+ align-items: center;
1575
+ justify-content: center;
1576
+ font-size: 18px;
1577
+ animation: pulse-border 2.5s infinite;
1578
+ }
1579
+
1580
+ @keyframes pulse-border {
1581
+ 0%, 100% { box-shadow: 0 0 0 0 rgba(0, 229, 255, 0.4); }
1582
+ 50% { box-shadow: 0 0 12px 2px rgba(0, 229, 255, 0.3); }
1583
+ }
1584
+
1585
+ .approval-alert-box {
1586
+ animation: slide-down 0.3s ease-out;
1587
+ }
1588
+
1589
+ @keyframes slide-down {
1590
+ from { opacity: 0; transform: translateY(-8px); }
1591
+ to { opacity: 1; transform: translateY(0); }
1592
+ }
1593
+
1594
+ .agent-plan-step-item {
1595
+ display: flex;
1596
+ align-items: center;
1597
+ gap: 10px;
1598
+ padding: 6px 10px;
1599
+ border-radius: 4px;
1600
+ font-size: 0.82rem;
1601
+ background: rgba(255, 255, 255, 0.03);
1602
+ border: 1px solid rgba(255, 255, 255, 0.04);
1603
+ }
1604
+
1605
+ .agent-plan-step-item.step-completed {
1606
+ border-left: 3px solid #10b981;
1607
+ }
1608
+
1609
+ .agent-plan-step-item.step-in_progress {
1610
+ border-left: 3px solid #38bdf8;
1611
+ background: rgba(56, 189, 248, 0.08);
1612
+ }
1613
+
1614
+ .agent-plan-step-item.step-failed {
1615
+ border-left: 3px solid #ef4444;
1616
+ background: rgba(239, 68, 68, 0.08);
1617
+ }
1618
+
1619
+ .agent-plan-step-item.step-pending {
1620
+ opacity: 0.65;
1621
+ }
1622
+