ft-scout 9.0.0 → 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 (57) 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/commands/plan.d.ts.map +1 -1
  7. package/bin/src/commands/plan.js +19 -5
  8. package/bin/src/commands/plan.js.map +1 -1
  9. package/bin/src/engine/actionVerifier.d.ts.map +1 -0
  10. package/bin/src/engine/actionVerifier.js +223 -0
  11. package/bin/src/engine/actionVerifier.js.map +1 -0
  12. package/bin/src/engine/agentEngine.d.ts.map +1 -1
  13. package/bin/src/engine/agentEngine.js +233 -1
  14. package/bin/src/engine/agentEngine.js.map +1 -1
  15. package/bin/src/engine/agentState.d.ts.map +1 -0
  16. package/bin/src/engine/agentState.js +445 -0
  17. package/bin/src/engine/agentState.js.map +1 -0
  18. package/bin/src/engine/appControl.d.ts.map +1 -1
  19. package/bin/src/engine/appControl.js +19 -6
  20. package/bin/src/engine/appControl.js.map +1 -1
  21. package/bin/src/engine/browser/browserController.d.ts.map +1 -0
  22. package/bin/src/engine/browser/browserController.js +689 -0
  23. package/bin/src/engine/browser/browserController.js.map +1 -0
  24. package/bin/src/engine/browser/index.d.ts.map +1 -0
  25. package/bin/src/engine/browser/index.js +2 -0
  26. package/bin/src/engine/browser/index.js.map +1 -0
  27. package/bin/src/engine/computer/computerController.d.ts.map +1 -0
  28. package/bin/src/engine/computer/computerController.js +295 -0
  29. package/bin/src/engine/computer/computerController.js.map +1 -0
  30. package/bin/src/engine/computer/index.d.ts.map +1 -0
  31. package/bin/src/engine/computer/index.js +2 -0
  32. package/bin/src/engine/computer/index.js.map +1 -0
  33. package/bin/src/engine/planner.d.ts.map +1 -0
  34. package/bin/src/engine/planner.js +242 -0
  35. package/bin/src/engine/planner.js.map +1 -0
  36. package/bin/src/engine/safety.d.ts.map +1 -0
  37. package/bin/src/engine/safety.js +281 -0
  38. package/bin/src/engine/safety.js.map +1 -0
  39. package/bin/src/engine/toolRegistry.d.ts.map +1 -0
  40. package/bin/src/engine/toolRegistry.js +218 -0
  41. package/bin/src/engine/toolRegistry.js.map +1 -0
  42. package/bin/src/index.js +6 -4
  43. package/bin/src/index.js.map +1 -1
  44. package/bin/src/server/server.d.ts.map +1 -1
  45. package/bin/src/server/server.js +116 -0
  46. package/bin/src/server/server.js.map +1 -1
  47. package/bin/src/utils/branding.js +1 -1
  48. package/bin/src/utils/payment.d.ts.map +1 -1
  49. package/bin/src/utils/payment.js +23 -3
  50. package/bin/src/utils/payment.js.map +1 -1
  51. package/bin/src/utils/pricing.d.ts.map +1 -1
  52. package/bin/src/utils/pricing.js +32 -10
  53. package/bin/src/utils/pricing.js.map +1 -1
  54. package/downloads/INVOICE_2026_ACME.pdf +1 -0
  55. package/package.json +1 -1
  56. package/web/app.js +294 -42
  57. 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.0
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,12 +407,14 @@ function openCheckoutModal(targetTier) {
376
407
  let quota = '100 Credits / mo';
377
408
 
378
409
  if (targetTier === 'pro') {
379
- const inr = isAnnual ? (p.pro?.annualPriceINR || 45) : (p.pro?.monthlyPriceINR || 50);
380
- 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();
381
413
  quota = `${(p.pro?.credits || 1000).toLocaleString()} Credits / mo`;
382
414
  } else if (targetTier === 'enterprise') {
383
- const inr = isAnnual ? (p.enterprise?.annualPriceINR || 37990) : (p.enterprise?.monthlyPriceINR || 3999);
384
- 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();
385
418
  quota = `${(p.enterprise?.credits || 10000).toLocaleString()} Credits / mo`;
386
419
  }
387
420
 
@@ -389,7 +422,7 @@ function openCheckoutModal(targetTier) {
389
422
  if (getEl('checkout-billing-cycle')) getEl('checkout-billing-cycle').innerText = isAnnual ? 'Billed Annually (Save more)' : 'Billed Monthly';
390
423
  if (getEl('checkout-plan-price')) getEl('checkout-plan-price').innerText = price;
391
424
  const checkoutCurrency = document.querySelector('.checkout-price-display .price-currency');
392
- if (checkoutCurrency) checkoutCurrency.innerText = '₹';
425
+ if (checkoutCurrency) checkoutCurrency.innerText = '$';
393
426
  if (getEl('checkout-price-period')) getEl('checkout-price-period').innerText = isAnnual ? '/ mo (annual)' : '/ mo';
394
427
  if (getEl('checkout-quota-val')) getEl('checkout-quota-val').innerText = quota;
395
428
 
@@ -448,46 +481,48 @@ function confirmScheduleDowngrade() {
448
481
 
449
482
  function confirmImmediateDowngrade() {
450
483
  const targetTier = pendingDowngradeTier || 'free';
484
+ const previousPaidTier = state.paidTier || (state.currentTier !== 'free' ? state.currentTier : null);
485
+
451
486
  state.currentTier = targetTier;
452
- state.paidTier = targetTier === 'free' ? null : targetTier;
453
- state.cancelAtPeriodEnd = false;
454
- 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;
455
494
  state.creditsTotal = targetTier === 'pro' ? 1000 : (targetTier === 'enterprise' ? 10000 : 100);
456
- state.creditsUsed = 0;
457
- const now = new Date();
458
- state.renewAt = new Date(now.getFullYear(), now.getMonth() + 1, 1, 0, 0, 0).toISOString();
495
+
459
496
  saveSessionToStorage();
460
497
  if (state.serverConnected) syncAuthWithServer();
461
498
  closeDowngradeModal();
462
499
  renderAllDynamicComponents();
463
- 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');
464
501
  }
465
502
 
466
503
  function resumeSubscription() {
467
- if (!state.currentTier || state.currentTier === 'free') {
468
- if (state.paidTier && state.paidTier !== 'free' && !isSubscriptionExpired()) {
469
- state.currentTier = state.paidTier;
470
- state.creditsTotal = state.currentTier === 'pro' ? 1000 : 10000;
471
- } else {
472
- return;
473
- }
474
- }
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;
475
507
  state.cancelAtPeriodEnd = false;
476
508
  state.pendingDowngrade = null;
477
- state.paidTier = state.currentTier;
509
+ state.creditsTotal = resumeTier === 'pro' ? 1000 : (resumeTier === 'enterprise' ? 10000 : 100);
510
+
478
511
  saveSessionToStorage();
479
512
  if (state.serverConnected) syncAuthWithServer();
480
513
  renderAllDynamicComponents();
481
- 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');
482
515
  }
483
516
 
484
517
  // Render and initialize Razorpay button UI
485
518
  function renderRazorpayButton(targetTier) {
486
519
  const isAnnual = state.billingCycle === 'annual';
487
520
  const p = state.pricing || {};
521
+ const isProOffer = p.pro?.offerActive && !isAnnual && p.pro?.offerPriceINR !== undefined;
522
+ const isEntOffer = p.enterprise?.offerActive && !isAnnual && p.enterprise?.offerPriceINR !== undefined;
488
523
  const inrAmount = targetTier === 'enterprise'
489
- ? (isAnnual ? (p.enterprise?.annualPriceINR || 37990) : (p.enterprise?.monthlyPriceINR || 3999))
490
- : (isAnnual ? (p.pro?.annualPriceINR || 45) : (p.pro?.monthlyPriceINR || 50));
524
+ ? (isEntOffer ? p.enterprise.offerPriceINR : (isAnnual ? (p.enterprise?.annualPriceINR ?? 37990) : (p.enterprise?.monthlyPriceINR ?? 3999)))
525
+ : (isProOffer ? p.pro.offerPriceINR : (isAnnual ? (p.pro?.annualPriceINR ?? 45) : (p.pro?.monthlyPriceINR ?? 50)));
491
526
  const btnRzp = getEl('btn-razorpay-checkout');
492
527
  if (!btnRzp) return;
493
528
 
@@ -503,9 +538,11 @@ async function handleRazorpayCheckout() {
503
538
  const targetTier = pendingCheckoutTier || 'pro';
504
539
  const isAnnual = state.billingCycle === 'annual';
505
540
  const p = state.pricing || {};
541
+ const isProOffer = p.pro?.offerActive && !isAnnual && p.pro?.offerPriceINR !== undefined;
542
+ const isEntOffer = p.enterprise?.offerActive && !isAnnual && p.enterprise?.offerPriceINR !== undefined;
506
543
  const inrAmount = targetTier === 'enterprise'
507
- ? (isAnnual ? (p.enterprise?.annualPriceINR || 37990) : (p.enterprise?.monthlyPriceINR || 3999))
508
- : (isAnnual ? (p.pro?.annualPriceINR || 45) : (p.pro?.monthlyPriceINR || 50));
544
+ ? (isEntOffer ? p.enterprise.offerPriceINR : (isAnnual ? (p.enterprise?.annualPriceINR ?? 37990) : (p.enterprise?.monthlyPriceINR ?? 3999)))
545
+ : (isProOffer ? p.pro.offerPriceINR : (isAnnual ? (p.pro?.annualPriceINR ?? 45) : (p.pro?.monthlyPriceINR ?? 50)));
509
546
  const amountInPaise = inrAmount * 100; // minimum 100 paise
510
547
 
511
548
  const btnRzp = getEl('btn-razorpay-checkout');
@@ -1044,7 +1081,7 @@ function initTerminalSimulator() {
1044
1081
  `;
1045
1082
  } else if (lower.includes('status')) {
1046
1083
  response.innerHTML = `
1047
- <span class="term-green">✔ Scout CLI Engine: v9.0.0 Online</span><br>
1084
+ <span class="term-green">✔ Scout CLI Engine: v9.0.2 Online</span><br>
1048
1085
  ↳ User: <strong>${escapeHtml(state.user ? state.user.name : 'Guest Session')}</strong><br>
1049
1086
  ↳ Active Subscription: <strong>${escapeHtml(TIER_DETAILS[state.currentTier].name)}</strong><br>
1050
1087
  ↳ Credit Quota: <strong>${state.byokEnabled ? 'BYOK Mode (Unlimited)' : `${state.creditsUsed} / ${state.creditsTotal} Credits Used`}</strong><br>
@@ -1289,18 +1326,33 @@ function renderTiersUI() {
1289
1326
 
1290
1327
  const isAnnual = state.billingCycle === 'annual';
1291
1328
  const p = state.pricing || {};
1292
- const proINR = isAnnual ? (p.pro?.annualPriceINR || 45) : (p.pro?.monthlyPriceINR || 50);
1293
- const entINR = isAnnual ? (p.enterprise?.annualPriceINR || 37990) : (p.enterprise?.monthlyPriceINR || 3999);
1329
+ const isProOffer = p.pro?.offerActive && !isAnnual && p.pro?.offerPriceUSD !== undefined;
1330
+ const isEntOffer = p.enterprise?.offerActive && !isAnnual && p.enterprise?.offerPriceUSD !== undefined;
1294
1331
 
1295
- if (getEl('price-pro-val')) getEl('price-pro-val').innerText = proINR;
1296
- if (getEl('price-pro-period')) getEl('price-pro-period').innerText = isAnnual ? '/ month (billed annually)' : '/ month';
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));
1334
+
1335
+ if (getEl('price-pro-val')) {
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}`;
1338
+ } else {
1339
+ getEl('price-pro-val').innerText = proUSD;
1340
+ }
1341
+ }
1342
+ if (getEl('price-pro-period')) getEl('price-pro-period').innerText = isProOffer ? `/ month (${p.pro?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '(billed annually)' : '/ month');
1297
1343
  const proCurrency = document.querySelector('#tier-card-pro .price-currency');
1298
- if (proCurrency) proCurrency.innerText = '₹';
1344
+ if (proCurrency) proCurrency.innerText = isProOffer ? '' : '$';
1299
1345
 
1300
- if (getEl('price-ent-val')) getEl('price-ent-val').innerText = entINR.toLocaleString('en-IN');
1301
- if (getEl('price-ent-period')) getEl('price-ent-period').innerText = isAnnual ? '/ month (billed annually)' : '/ month';
1346
+ if (getEl('price-ent-val')) {
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}`;
1349
+ } else {
1350
+ getEl('price-ent-val').innerText = entUSD;
1351
+ }
1352
+ }
1353
+ if (getEl('price-ent-period')) getEl('price-ent-period').innerText = isEntOffer ? `/ month (${p.enterprise?.offerBadge || 'PROMO OFFER'})` : (isAnnual ? '(billed annually)' : '/ month');
1302
1354
  const entCurrency = document.querySelector('#tier-card-enterprise .price-currency');
1303
- if (entCurrency) entCurrency.innerText = '₹';
1355
+ if (entCurrency) entCurrency.innerText = isEntOffer ? '' : '$';
1304
1356
 
1305
1357
  ['free', 'pro', 'enterprise'].forEach((t) => {
1306
1358
  const card = getEl(`tier-card-${t}`);
@@ -1333,7 +1385,7 @@ function renderTiersUI() {
1333
1385
  } else if (t === 'pro') {
1334
1386
  if (state.paidTier === 'pro' && !isSubscriptionExpired()) {
1335
1387
  btn.className = 'btn btn-primary btn-block btn-select-tier';
1336
- 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)`;
1337
1389
  btn.disabled = false;
1338
1390
  } else if (state.currentTier === 'enterprise') {
1339
1391
  if (isScheduledCancel && state.pendingDowngrade === 'pro') {
@@ -1353,7 +1405,7 @@ function renderTiersUI() {
1353
1405
  } else if (t === 'enterprise') {
1354
1406
  if (state.paidTier === 'enterprise' && !isSubscriptionExpired()) {
1355
1407
  btn.className = 'btn btn-warning btn-block btn-select-tier';
1356
- 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)`;
1357
1409
  btn.disabled = false;
1358
1410
  } else {
1359
1411
  btn.className = 'btn btn-outline btn-block btn-select-tier';
@@ -2280,3 +2332,203 @@ function initPacmanGame() {
2280
2332
 
2281
2333
  render();
2282
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.0
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.0
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
+