srishtichugh commited on
Commit
c06d0c3
Β·
1 Parent(s): 869f731

animate ui

Browse files
Files changed (1) hide show
  1. ui/index.html +541 -393
ui/index.html CHANGED
@@ -7,7 +7,7 @@
7
  <title>OrgOS β€” Enterprise RL Environment</title>
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link
10
- href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@300;400;500;600&family=DM+Mono:wght@400;500&display=swap"
11
  rel="stylesheet">
12
  <script src="https://cdn.tailwindcss.com"></script>
13
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
@@ -17,42 +17,34 @@
17
  *::before,
18
  *::after {
19
  box-sizing: border-box;
 
 
20
  }
21
 
22
  :root {
23
  --font-sans: 'DM Sans', sans-serif;
24
  --font-mono: 'DM Mono', monospace;
25
-
26
- /* OrgOS neutrals */
27
  --bg: #F5F6F8;
28
  --surface: #FFFFFF;
29
  --border: #E3E6EA;
30
  --text-1: #111827;
31
  --text-2: #6B7280;
32
  --text-3: #9CA3AF;
33
-
34
- /* App brand colors */
 
35
  --jira: #0052CC;
36
  --jira-light: #E9F0FF;
37
  --jira-mid: #B3C8F0;
38
-
39
  --zendesk: #03363D;
40
  --zendesk-light: #E6F3F4;
41
  --zendesk-mid: #B2D8DB;
42
-
43
  --sf: #00A1E0;
44
  --sf-light: #E5F6FD;
45
  --sf-mid: #B3E3F8;
46
-
47
  --wd: #FF6B35;
48
  --wd-light: #FFF0EB;
49
  --wd-mid: #FFD0C0;
50
-
51
- /* Status */
52
- --green: #10B981;
53
- --red: #EF4444;
54
- --amber: #F59E0B;
55
- --violet: #7C3AED;
56
  }
57
 
58
  body {
@@ -63,7 +55,6 @@
63
  line-height: 1.5;
64
  }
65
 
66
- /* ---- Scrollbars ---- */
67
  ::-webkit-scrollbar {
68
  width: 5px;
69
  height: 5px;
@@ -82,7 +73,7 @@
82
  background: var(--text-3);
83
  }
84
 
85
- /* ---- Animations ---- */
86
  @keyframes fadeUp {
87
  from {
88
  opacity: 0;
@@ -103,43 +94,79 @@
103
  }
104
 
105
  50% {
106
- opacity: 0.4;
107
  }
108
  }
109
 
110
- @keyframes drift-in {
111
  from {
 
112
  opacity: 0;
113
- transform: translateX(12px);
114
  }
115
 
116
  to {
 
117
  opacity: 1;
118
- transform: translateX(0);
119
  }
120
  }
121
 
122
- @keyframes checkmark {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  from {
124
- transform: scale(0) rotate(-20deg);
125
  opacity: 0;
 
126
  }
127
 
128
  to {
129
- transform: scale(1) rotate(0);
130
  opacity: 1;
 
131
  }
132
  }
133
 
134
- @keyframes score-flash {
135
 
136
  0%,
137
  100% {
138
- background: transparent;
139
  }
140
 
141
- 40% {
142
- background: rgba(16, 185, 129, 0.12);
143
  }
144
  }
145
 
@@ -147,10 +174,6 @@
147
  animation: fadeUp 0.25s ease forwards;
148
  }
149
 
150
- .drift-in {
151
- animation: drift-in 0.2s ease forwards;
152
- }
153
-
154
  .live-dot {
155
  animation: pulse-dot 1.4s ease-in-out infinite;
156
  }
@@ -163,11 +186,15 @@
163
  animation: score-flash 0.5s ease;
164
  }
165
 
 
 
 
 
166
  [x-cloak] {
167
  display: none !important;
168
  }
169
 
170
- /* ---- Top bar ---- */
171
  .topbar {
172
  background: var(--surface);
173
  border-bottom: 1px solid var(--border);
@@ -175,7 +202,7 @@
175
  display: flex;
176
  align-items: center;
177
  padding: 0 20px;
178
- gap: 16px;
179
  position: sticky;
180
  top: 0;
181
  z-index: 100;
@@ -206,7 +233,6 @@
206
  .logo-sub {
207
  font-size: 11px;
208
  color: var(--text-3);
209
- font-weight: 400;
210
  }
211
 
212
  .divider {
@@ -220,7 +246,7 @@
220
  background: var(--bg);
221
  border: 1px solid var(--border);
222
  border-radius: 7px;
223
- padding: 5px 10px;
224
  font-family: var(--font-sans);
225
  font-size: 12px;
226
  color: var(--text-1);
@@ -228,15 +254,9 @@
228
  outline: none;
229
  cursor: pointer;
230
  appearance: none;
231
- padding-right: 28px;
232
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236B7280' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
233
  background-repeat: no-repeat;
234
  background-position: right 9px center;
235
- transition: border-color 0.15s;
236
- }
237
-
238
- .wf-select:focus {
239
- border-color: #6B7280;
240
  }
241
 
242
  .btn {
@@ -296,7 +316,7 @@
296
  font-weight: 500;
297
  }
298
 
299
- /* ---- Three-col layout ---- */
300
  .layout {
301
  display: grid;
302
  grid-template-columns: 260px 1fr 240px;
@@ -304,7 +324,7 @@
304
  overflow: hidden;
305
  }
306
 
307
- /* ---- Left sidebar ---- */
308
  .sidebar-left {
309
  background: var(--surface);
310
  border-right: 1px solid var(--border);
@@ -328,7 +348,6 @@
328
  margin-bottom: 10px;
329
  }
330
 
331
- /* Workflow steps */
332
  .step-row {
333
  display: flex;
334
  align-items: flex-start;
@@ -369,11 +388,10 @@
369
  }
370
 
371
  .step-desc.done {
372
- color: var(--text-3);
373
- text-decoration: line-through;
374
  }
375
 
376
- /* Schema drift pills */
377
  .drift-pill {
378
  display: flex;
379
  align-items: center;
@@ -409,7 +427,6 @@
409
  margin-left: auto;
410
  }
411
 
412
- /* Rules */
413
  .rule-row {
414
  display: flex;
415
  justify-content: space-between;
@@ -429,7 +446,7 @@
429
  color: var(--text-1);
430
  }
431
 
432
- /* ---- Center panel β€” App tabs ---- */
433
  .center-panel {
434
  display: flex;
435
  flex-direction: column;
@@ -437,7 +454,112 @@
437
  background: var(--bg);
438
  }
439
 
440
- /* App tab strip */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  .app-tabs {
442
  display: flex;
443
  background: var(--surface);
@@ -470,6 +592,10 @@
470
  border-bottom-color: var(--text-1);
471
  }
472
 
 
 
 
 
473
  .app-tab .app-dot {
474
  width: 8px;
475
  height: 8px;
@@ -491,7 +617,20 @@
491
  color: #fff;
492
  }
493
 
494
- /* ---- JIRA-like UI ---- */
 
 
 
 
 
 
 
 
 
 
 
 
 
495
  .jira-header {
496
  background: var(--jira);
497
  padding: 10px 20px;
@@ -508,8 +647,11 @@
508
  }
509
 
510
  .jira-project {
511
- font-size: 12px;
512
  color: rgba(255, 255, 255, 0.7);
 
 
 
513
  }
514
 
515
  .jira-nav {
@@ -524,7 +666,6 @@
524
  font-size: 12px;
525
  color: rgba(255, 255, 255, 0.8);
526
  cursor: pointer;
527
- transition: background 0.1s;
528
  }
529
 
530
  .jira-nav-item:hover {
@@ -577,7 +718,7 @@
577
  margin-bottom: 6px;
578
  border: 1px solid var(--border);
579
  cursor: pointer;
580
- transition: box-shadow 0.15s;
581
  }
582
 
583
  .jira-card:hover {
@@ -586,7 +727,8 @@
586
 
587
  .jira-card.highlighted {
588
  border-color: var(--jira);
589
- box-shadow: 0 0 0 2px rgba(0, 82, 204, 0.15);
 
590
  }
591
 
592
  .jira-card-title {
@@ -641,7 +783,7 @@
641
  color: var(--jira);
642
  }
643
 
644
- /* ---- ZENDESK-like UI ---- */
645
  .zd-header {
646
  background: var(--zendesk);
647
  padding: 10px 20px;
@@ -743,7 +885,7 @@
743
  margin-bottom: 4px;
744
  font-size: 12px;
745
  cursor: pointer;
746
- transition: box-shadow 0.15s;
747
  }
748
 
749
  .zd-ticket-row:hover {
@@ -752,6 +894,8 @@
752
 
753
  .zd-ticket-row.highlighted {
754
  border-color: var(--zendesk);
 
 
755
  }
756
 
757
  .zd-tid {
@@ -765,7 +909,8 @@
765
  color: var(--text-1);
766
  }
767
 
768
- .zd-urgency {
 
769
  text-align: center;
770
  }
771
 
@@ -798,10 +943,6 @@
798
  color: var(--text-2);
799
  }
800
 
801
- .zd-status {
802
- text-align: center;
803
- }
804
-
805
  .status-badge {
806
  display: inline-block;
807
  width: 8px;
@@ -825,7 +966,7 @@
825
  background: var(--red);
826
  }
827
 
828
- /* ---- SALESFORCE-like UI ---- */
829
  .sf-header {
830
  background: var(--sf);
831
  padding: 10px 20px;
@@ -878,7 +1019,6 @@
878
 
879
  .sf-stages {
880
  display: flex;
881
- gap: 0;
882
  margin-bottom: 16px;
883
  border-radius: 6px;
884
  overflow: hidden;
@@ -895,7 +1035,6 @@
895
  text-transform: uppercase;
896
  letter-spacing: 0.05em;
897
  color: var(--sf);
898
- position: relative;
899
  }
900
 
901
  .sf-stage.active {
@@ -918,7 +1057,7 @@
918
  gap: 12px;
919
  align-items: center;
920
  cursor: pointer;
921
- transition: box-shadow 0.15s;
922
  }
923
 
924
  .sf-account-card:hover {
@@ -927,7 +1066,8 @@
927
 
928
  .sf-account-card.highlighted {
929
  border-color: var(--sf);
930
- box-shadow: 0 0 0 2px rgba(0, 161, 224, 0.15);
 
931
  }
932
 
933
  .sf-avatar {
@@ -982,7 +1122,7 @@
982
  color: var(--text-1);
983
  }
984
 
985
- /* ---- WORKDAY-like UI ---- */
986
  .wd-header {
987
  background: var(--wd);
988
  padding: 10px 20px;
@@ -1038,7 +1178,7 @@
1038
  font-size: 12px;
1039
  align-items: center;
1040
  cursor: pointer;
1041
- transition: box-shadow 0.15s;
1042
  }
1043
 
1044
  .wd-task-row:hover {
@@ -1047,6 +1187,8 @@
1047
 
1048
  .wd-task-row.highlighted {
1049
  border-color: var(--wd);
 
 
1050
  }
1051
 
1052
  .wd-emp {
@@ -1091,16 +1233,15 @@
1091
  margin-top: 2px;
1092
  }
1093
 
1094
- /* ---- Agent log in center ---- */
1095
  .agent-log {
1096
- flex: 1;
1097
  display: flex;
1098
  flex-direction: column;
1099
  background: var(--surface);
1100
  border-top: 1px solid var(--border);
1101
  overflow: hidden;
1102
- min-height: 200px;
1103
- max-height: 280px;
1104
  }
1105
 
1106
  .log-header {
@@ -1231,7 +1372,7 @@
1231
  line-height: 1.4;
1232
  }
1233
 
1234
- /* ---- Right sidebar ---- */
1235
  .sidebar-right {
1236
  background: var(--surface);
1237
  border-left: 1px solid var(--border);
@@ -1240,7 +1381,6 @@
1240
  overflow: hidden;
1241
  }
1242
 
1243
- /* Score gauge */
1244
  .score-block {
1245
  padding: 20px 16px 16px;
1246
  border-bottom: 1px solid var(--border);
@@ -1282,7 +1422,6 @@
1282
  margin-top: 2px;
1283
  }
1284
 
1285
- /* Breakdown bars */
1286
  .breakdown-block {
1287
  padding: 14px 16px;
1288
  border-bottom: 1px solid var(--border);
@@ -1323,7 +1462,6 @@
1323
  transition: width 0.4s ease;
1324
  }
1325
 
1326
- /* Episode stats */
1327
  .stats-block {
1328
  padding: 14px 16px;
1329
  border-bottom: 1px solid var(--border);
@@ -1349,7 +1487,6 @@
1349
  color: var(--text-1);
1350
  }
1351
 
1352
- /* Violations */
1353
  .violations-block {
1354
  padding: 14px 16px;
1355
  flex: 1;
@@ -1368,17 +1505,18 @@
1368
  border-bottom: none;
1369
  }
1370
 
1371
- /* Policy drift banner */
1372
  .drift-banner {
1373
  background: linear-gradient(90deg, #FFFBEB, #FEF3C7);
1374
  border: 1px solid #FDE68A;
1375
  border-radius: 7px;
1376
  padding: 8px 12px;
1377
- margin: 12px 16px 0;
1378
  display: flex;
1379
  align-items: center;
1380
  gap: 8px;
1381
- animation: fadeUp 0.3s ease;
 
1382
  }
1383
 
1384
  .drift-banner-dot {
@@ -1395,20 +1533,15 @@
1395
  color: #92400E;
1396
  }
1397
 
1398
- /* App content area (flex-1 scrollable) */
1399
- .app-content {
1400
- flex: 1;
1401
- overflow: hidden;
1402
- display: flex;
1403
- flex-direction: column;
1404
- }
1405
-
1406
- .app-inner {
1407
- flex: 1;
1408
- overflow-y: auto;
1409
  }
1410
 
1411
- /* Live indicator */
1412
  .live-badge {
1413
  display: flex;
1414
  align-items: center;
@@ -1424,40 +1557,26 @@
1424
  border-radius: 50%;
1425
  background: var(--green);
1426
  }
1427
-
1428
- /* Schema version pills in header */
1429
- .schema-pill {
1430
- background: rgba(255, 255, 255, 0.18);
1431
- border-radius: 4px;
1432
- padding: 2px 8px;
1433
- font-size: 10px;
1434
- color: rgba(255, 255, 255, 0.9);
1435
- font-family: var(--font-mono);
1436
- }
1437
  </style>
1438
  </head>
1439
 
1440
  <body x-data="orgos()" x-init="init()">
1441
 
1442
- <!-- TOP BAR -->
1443
  <header class="topbar">
1444
  <div class="logo-mark">OS</div>
1445
  <div>
1446
  <div class="logo-text">OrgOS</div>
1447
  </div>
1448
  <div class="logo-sub">Enterprise RL Environment</div>
1449
-
1450
  <div class="divider"></div>
1451
-
1452
  <label style="font-size:11px;color:var(--text-3);font-weight:500;">Workflow</label>
1453
  <select class="wf-select" x-model="selectedWorkflow">
1454
  <option value="A">A β€” Customer Bug Fix</option>
1455
  <option value="B">B β€” Employee Onboarding</option>
1456
  <option value="C">C β€” Churn Risk Alert</option>
1457
  </select>
1458
-
1459
  <button class="btn btn-ghost" @click="resetEpisode()" :disabled="isRunning">Reset</button>
1460
-
1461
  <button class="btn" :class="isRunning ? 'btn-danger' : 'btn-primary'"
1462
  @click="isRunning ? stopAgent() : startAgent()">
1463
  <svg x-show="!isRunning" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
@@ -1470,66 +1589,56 @@
1470
  </svg>
1471
  <span x-text="isRunning ? 'Stop' : 'Run Agent'"></span>
1472
  </button>
1473
-
1474
  <div class="divider"></div>
1475
-
1476
- <!-- Score in topbar -->
1477
  <div style="display:flex;align-items:baseline;gap:5px;">
1478
  <span style="font-size:11px;color:var(--text-3);font-weight:500;">Score</span>
1479
  <span style="font-size:18px;font-weight:600;color:var(--text-1);font-family:var(--font-mono);"
1480
  :class="scoreUpdated ? 'score-flash' : ''" x-text="currentScore.toFixed(3)"></span>
1481
  </div>
1482
-
1483
  <div style="display:flex;align-items:baseline;gap:5px;">
1484
  <span style="font-size:11px;color:var(--text-3);font-weight:500;">Step</span>
1485
  <span style="font-size:18px;font-weight:600;color:var(--text-1);font-family:var(--font-mono);"
1486
- x-text="stepCount + '/' + maxSteps"></span>
1487
  </div>
1488
-
1489
  <div x-show="policyDriftActive" class="badge" style="background:#FEF3C7;color:#92400E;border:1px solid #FDE68A;">
1490
- Policy Drift Active
1491
- </div>
1492
-
1493
- <div class="ml-auto" style="display:flex;align-items:center;gap:6px;">
1494
- <div class="live-badge-dot" :class="serverHealthy ? 'live-dot' : ''"
1495
- :style="serverHealthy ? '' : 'background:var(--red)'"></div>
1496
- <span style="font-size:11px;" :class="serverHealthy ? 'live-badge' : ''"
1497
- :style="!serverHealthy ? 'color:var(--red);font-size:11px;' : ''"
1498
- x-text="serverHealthy ? 'Connected' : 'Offline'"></span>
1499
  </div>
1500
  </header>
1501
 
1502
- <!-- THREE-COLUMN LAYOUT -->
1503
  <div class="layout">
1504
 
1505
- <!-- LEFT SIDEBAR: Workflow + Schema + Rules -->
1506
  <aside class="sidebar-left">
1507
 
1508
- <!-- Workflow progress -->
1509
  <div class="sidebar-section" style="flex:1;overflow-y:auto;">
1510
  <div class="section-label">
1511
- Workflow
1512
- <span style="color:var(--text-1);font-weight:700;" x-text="workflowId || selectedWorkflow"></span>
1513
  Progress
1514
  </div>
1515
  <div style="margin-bottom:12px;">
1516
  <div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text-2);margin-bottom:5px;">
1517
- <span x-text="completedSteps.length + ' of ' + totalSteps + ' steps done'"></span>
1518
  <span style="font-weight:600;color:var(--text-1);"
1519
- x-text="Math.round(completedSteps.length/Math.max(totalSteps,1)*100) + '%'"></span>
1520
  </div>
1521
  <div style="height:4px;background:var(--bg);border-radius:2px;">
1522
  <div style="height:4px;background:var(--green);border-radius:2px;transition:width 0.4s ease;"
1523
- :style="'width:' + (completedSteps.length/Math.max(totalSteps,1)*100) + '%'"></div>
1524
  </div>
1525
  </div>
1526
  <div x-show="workflowGoal"
1527
  style="font-size:11px;color:var(--text-2);line-height:1.5;margin-bottom:12px;padding:8px;background:var(--bg);border-radius:6px;"
1528
  x-text="workflowGoal"></div>
1529
-
1530
- <template x-for="(step, i) in allSteps" :key="i">
1531
  <div class="step-row">
1532
- <div class="step-num" :class="completedSteps.includes(step.id) ? 'done' : ''">
1533
  <template x-if="completedSteps.includes(step.id)">
1534
  <svg class="step-check" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
1535
  <path
@@ -1540,46 +1649,42 @@
1540
  <span x-text="step.id"></span>
1541
  </template>
1542
  </div>
1543
- <span class="step-desc" :class="completedSteps.includes(step.id) ? 'done' : ''"
1544
  x-text="step.description"></span>
1545
  </div>
1546
  </template>
1547
  </div>
1548
 
1549
- <!-- Schema drift -->
1550
  <div class="sidebar-section">
1551
  <div class="section-label">Schema Drift</div>
1552
- <div x-show="Object.keys(schemaHints).length === 0" style="font-size:11px;color:var(--text-3);">
1553
- No drift β€” v1 canonical names active.
1554
- </div>
1555
- <template x-for="[field, drifted] in Object.entries(schemaHints)" :key="field">
1556
  <div class="drift-pill">
1557
- <span class="drift-old" x-text="field.split('.')[1] ?? field"></span>
1558
  <span class="drift-arr">β†’</span>
1559
  <span class="drift-new" x-text="drifted"></span>
1560
- <span class="drift-app" x-text="field.split('.')[0] ?? ''"></span>
1561
  </div>
1562
  </template>
1563
  </div>
1564
 
1565
- <!-- Active rules -->
1566
  <div class="sidebar-section">
1567
  <div class="section-label">Active Rules</div>
1568
- <template x-for="[key, val] in Object.entries(activeRules)" :key="key">
1569
  <div class="rule-row">
1570
  <span class="rule-key" x-text="key.replace(/_/g,' ')"></span>
1571
  <span class="rule-val" x-text="val"></span>
1572
  </div>
1573
  </template>
1574
- <div x-show="Object.keys(activeRules).length === 0" style="font-size:11px;color:var(--text-3);">Reset to load
1575
  rules.</div>
1576
  </div>
1577
 
1578
- <!-- Schema versions -->
1579
  <div class="sidebar-section" style="border-bottom:none;">
1580
  <div class="section-label">Schema Versions</div>
1581
  <div style="display:flex;gap:6px;flex-wrap:wrap;">
1582
- <template x-for="[app, ver] in Object.entries(schemaVersions)" :key="app">
1583
  <div
1584
  style="background:var(--bg);border:1px solid var(--border);border-radius:5px;padding:3px 8px;font-size:11px;">
1585
  <span style="color:var(--text-3);" x-text="app.charAt(0).toUpperCase()+app.slice(1,3)"></span>
@@ -1587,29 +1692,42 @@
1587
  x-text="ver"></span>
1588
  </div>
1589
  </template>
1590
- <div x-show="Object.keys(schemaVersions).length === 0" style="font-size:11px;color:var(--text-3);">β€”</div>
1591
  </div>
1592
  </div>
1593
-
1594
  </aside>
1595
 
1596
- <!-- CENTER: App UIs + Agent Log -->
1597
  <main class="center-panel">
1598
 
1599
  <!-- Policy drift banner -->
1600
- <div x-show="policyDriftActive" class="drift-banner" style="flex-shrink:0;">
1601
  <div class="drift-banner-dot live-dot"></div>
1602
  <span class="drift-banner-text">Policy drift active β€” SLA and approval thresholds have tightened this
1603
  episode.</span>
1604
  </div>
1605
 
 
 
 
 
 
 
 
 
 
 
 
 
1606
  <!-- App tab strip -->
1607
- <div class="app-tabs" style="flex-shrink:0;">
1608
  <template x-for="tab in appTabs" :key="tab.id">
1609
- <div class="app-tab" :class="activeAppTab === tab.id ? 'active' : ''" @click="activeAppTab = tab.id">
1610
- <div class="app-dot" :style="'background:' + tab.color"></div>
 
 
1611
  <span x-text="tab.label"></span>
1612
- <template x-if="appOpenCounts[tab.id] > 0">
1613
  <span class="tab-count" x-text="appOpenCounts[tab.id]"></span>
1614
  </template>
1615
  </div>
@@ -1620,15 +1738,13 @@
1620
  <div class="app-content">
1621
  <div class="app-inner">
1622
 
1623
- <!-- ======================== JIRA ======================== -->
1624
- <div x-show="activeAppTab === 'jira'">
1625
  <div class="jira-header">
1626
  <span class="jira-logo">Jira</span>
1627
- <span class="jira-project"
1628
- style="background:rgba(255,255,255,0.15);border-radius:4px;padding:2px 8px;font-size:11px;">OrgOS /
1629
- Engineering</span>
1630
- <template x-for="[app, ver] in Object.entries(schemaVersions)" :key="app">
1631
- <span x-show="app === 'jira'" class="schema-pill" x-text="'schema ' + ver"></span>
1632
  </template>
1633
  <div class="jira-nav" style="margin-left:auto;">
1634
  <div class="jira-nav-item">Board</div>
@@ -1639,77 +1755,64 @@
1639
  <div class="jira-board">
1640
  <div style="font-size:12px;font-weight:600;color:var(--text-1);margin-bottom:14px;">Sprint Board</div>
1641
  <div class="jira-board-cols">
1642
- <!-- TO DO -->
1643
  <div>
1644
- <div class="jira-col-header">
1645
- To Do
1646
- <span class="jira-col-count" x-text="jiraCards.filter(c=>c.status==='open').length"></span>
1647
- </div>
1648
  <div class="jira-col-body">
1649
  <template x-for="card in jiraCards.filter(c=>c.status==='open')" :key="card.id">
1650
- <div class="jira-card" :class="card.highlighted ? 'highlighted' : ''">
1651
  <div class="jira-card-title" x-text="card.title"></div>
1652
  <div class="jira-card-meta">
1653
- <div class="priority-dot"
1654
- :class="card.priority === 'p0' ? 'p0' : card.priority === 'p1' ? 'p1' : 'p2'"></div>
1655
  <span class="jira-id" x-text="card.id"></span>
1656
  <template x-if="card.linked_zendesk">
1657
  <span
1658
  style="font-size:10px;background:var(--zendesk-light);color:var(--zendesk);border-radius:3px;padding:1px 5px;font-weight:500;"
1659
  x-text="card.linked_zendesk"></span>
1660
  </template>
1661
- <div class="jira-assignee"
1662
- x-text="card.assignee ? card.assignee.charAt(0).toUpperCase() : '?'"
1663
- :style="card.assignee ? '' : 'color:var(--text-3);'"></div>
1664
  </div>
1665
  </div>
1666
  </template>
1667
- <div x-show="jiraCards.filter(c=>c.status==='open').length === 0"
1668
  style="font-size:11px;color:var(--text-3);padding:12px;text-align:center;">No open issues</div>
1669
  </div>
1670
  </div>
1671
- <!-- IN PROGRESS -->
1672
  <div>
1673
- <div class="jira-col-header">
1674
- In Progress
1675
- <span class="jira-col-count" x-text="jiraCards.filter(c=>c.status==='in_progress').length"></span>
1676
- </div>
1677
  <div class="jira-col-body">
1678
  <template x-for="card in jiraCards.filter(c=>c.status==='in_progress')" :key="card.id">
1679
- <div class="jira-card" :class="card.highlighted ? 'highlighted' : ''">
1680
  <div class="jira-card-title" x-text="card.title"></div>
1681
  <div class="jira-card-meta">
1682
- <div class="priority-dot"
1683
- :class="card.priority === 'p0' ? 'p0' : card.priority === 'p1' ? 'p1' : 'p2'"></div>
1684
  <span class="jira-id" x-text="card.id"></span>
1685
- <div class="jira-assignee"
1686
- x-text="card.assignee ? card.assignee.charAt(0).toUpperCase() : '?'"></div>
1687
  </div>
1688
  </div>
1689
  </template>
1690
- <div x-show="jiraCards.filter(c=>c.status==='in_progress').length === 0"
1691
  style="font-size:11px;color:var(--text-3);padding:12px;text-align:center;">Nothing in progress
1692
  </div>
1693
  </div>
1694
  </div>
1695
- <!-- DONE -->
1696
  <div>
1697
- <div class="jira-col-header" style="color:var(--green);">
1698
- Done
1699
- <span class="jira-col-count"
1700
- x-text="jiraCards.filter(c=>c.status==='closed'||c.status==='done').length"></span>
1701
- </div>
1702
  <div class="jira-col-body">
1703
  <template x-for="card in jiraCards.filter(c=>c.status==='closed'||c.status==='done')"
1704
  :key="card.id">
1705
- <div class="jira-card" style="opacity:0.7;">
1706
  <div class="jira-card-title" style="text-decoration:line-through;" x-text="card.title"></div>
1707
- <div class="jira-card-meta">
1708
- <span class="jira-id" x-text="card.id"></span>
1709
- </div>
1710
  </div>
1711
  </template>
1712
- <div x-show="jiraCards.filter(c=>c.status==='closed'||c.status==='done').length === 0"
1713
  style="font-size:11px;color:var(--text-3);padding:12px;text-align:center;">Nothing done yet</div>
1714
  </div>
1715
  </div>
@@ -1717,35 +1820,29 @@
1717
  </div>
1718
  </div>
1719
 
1720
- <!-- ======================== ZENDESK ======================== -->
1721
- <div x-show="activeAppTab === 'zendesk'">
1722
  <div class="zd-header">
1723
  <span class="zd-logo">Zendesk</span>
1724
  <input class="zd-search" placeholder="Search tickets..." readonly />
1725
- <template x-for="[app, ver] in Object.entries(schemaVersions)" :key="app">
1726
- <span x-show="app === 'zendesk'" class="schema-pill" x-text="'schema ' + ver"></span>
1727
  </template>
1728
  <div style="margin-left:auto;display:flex;align-items:center;gap:8px;">
1729
  <div class="badge" style="background:rgba(255,255,255,0.15);color:#fff;font-size:10px;"
1730
- x-text="zdTickets.filter(t=>t.state==='open'||t.state==='new').length + ' open'"></div>
1731
  </div>
1732
  </div>
1733
  <div class="zd-layout" style="height:calc(100% - 42px);">
1734
  <div class="zd-sidebar">
1735
  <div class="zd-nav-group">
1736
  <div class="zd-nav-title">Views</div>
1737
- <div class="zd-nav-item active">
1738
- All tickets
1739
- <span class="zd-count" x-text="zdTickets.length"></span>
1740
- </div>
1741
- <div class="zd-nav-item">
1742
- Open
1743
- <span class="zd-count" x-text="zdTickets.filter(t=>t.state==='open').length"></span>
1744
- </div>
1745
- <div class="zd-nav-item">
1746
- New
1747
- <span class="zd-count" x-text="zdTickets.filter(t=>t.state==='new').length"></span>
1748
  </div>
 
 
 
 
1749
  <div class="zd-nav-item">Pending</div>
1750
  <div class="zd-nav-item">Resolved</div>
1751
  </div>
@@ -1757,7 +1854,6 @@
1757
  </div>
1758
  </div>
1759
  <div class="zd-tickets">
1760
- <!-- Header row -->
1761
  <div
1762
  style="display:grid;grid-template-columns:60px 1fr 80px 90px 80px;gap:12px;padding:6px 14px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.07em;color:var(--text-3);margin-bottom:4px;">
1763
  <span>ID</span><span>Subject</span><span
@@ -1765,32 +1861,32 @@
1765
  style="text-align:center;">Status</span>
1766
  </div>
1767
  <template x-for="ticket in zdTickets" :key="ticket.id">
1768
- <div class="zd-ticket-row" :class="ticket.highlighted ? 'highlighted' : ''">
1769
  <span class="zd-tid" x-text="ticket.id"></span>
1770
  <span class="zd-subject" x-text="ticket.subject"></span>
1771
  <div class="zd-urgency">
1772
  <span class="urgency-badge"
1773
  :class="ticket.urgency==='p0'||ticket.urgency==='high'?'urg-high':ticket.urgency==='p1'||ticket.urgency==='medium'?'urg-medium':'urg-low'"
1774
- x-text="ticket.urgency || 'p2'"></span>
1775
  </div>
1776
- <span class="zd-agent" x-text="ticket.agent || 'β€”'"></span>
1777
- <div class="zd-status" style="text-align:center;">
1778
  <span class="status-badge"
1779
  :class="ticket.state==='open'?'status-open':ticket.state==='pending'?'status-pending':ticket.state==='resolved'?'status-resolved':'status-new'"></span>
1780
  <span style="font-size:10px;color:var(--text-2);margin-left:4px;"
1781
- x-text="ticket.state || 'new'"></span>
1782
  </div>
1783
  </div>
1784
  </template>
1785
- <div x-show="zdTickets.length === 0"
1786
  style="font-size:12px;color:var(--text-3);padding:24px;text-align:center;">No tickets loaded β€” reset
1787
  to start an episode.</div>
1788
  </div>
1789
  </div>
1790
  </div>
1791
 
1792
- <!-- ======================== SALESFORCE ======================== -->
1793
- <div x-show="activeAppTab === 'salesforce'">
1794
  <div class="sf-header">
1795
  <span class="sf-logo">Salesforce</span>
1796
  <div class="sf-tabs">
@@ -1798,12 +1894,11 @@
1798
  <div class="sf-tab">Opportunities</div>
1799
  <div class="sf-tab">Pipeline</div>
1800
  </div>
1801
- <template x-for="[app, ver] in Object.entries(schemaVersions)" :key="app">
1802
- <span x-show="app === 'salesforce'" class="schema-pill" x-text="'schema ' + ver"></span>
1803
  </template>
1804
  </div>
1805
  <div class="sf-body">
1806
- <!-- Pipeline stages -->
1807
  <div class="sf-pipeline">
1808
  <div class="sf-pipeline-title">Deal Pipeline</div>
1809
  <div class="sf-stages">
@@ -1814,49 +1909,46 @@
1814
  <div class="sf-stage">Closed Won</div>
1815
  </div>
1816
  </div>
1817
-
1818
- <!-- Account cards -->
1819
  <div style="font-size:12px;font-weight:600;color:var(--text-1);margin-bottom:10px;">Accounts</div>
1820
  <template x-for="acct in sfAccounts" :key="acct.id">
1821
- <div class="sf-account-card" :class="acct.highlighted ? 'highlighted' : ''">
1822
  <div class="sf-avatar" x-text="acct.name.charAt(0)"></div>
1823
  <div>
1824
  <div class="sf-company" x-text="acct.name"></div>
1825
  <div class="sf-meta">
1826
  <span class="health-dot"
1827
  :class="acct.health==='green'?'health-green':acct.health==='yellow'?'health-yellow':'health-red'"></span>
1828
- <span x-text="acct.stage || 'Qualified'"></span>
1829
  <span style="margin:0 6px;color:var(--border);">Β·</span>
1830
- <span x-text="acct.owner ? 'Owner: ' + acct.owner : 'Unassigned'"></span>
1831
  <template x-if="acct.churn_risk">
1832
- <span style="margin-left:6px;" class="badge"
1833
- style="background:#FEE2E2;color:var(--red);font-size:10px;">Churn Risk</span>
1834
  </template>
1835
  </div>
1836
  </div>
1837
  <div style="text-align:right;">
1838
- <div class="sf-arr" x-text="acct.arr ? '$' + (acct.arr/1000).toFixed(0) + 'K' : 'β€”'"></div>
1839
  <div style="font-size:10px;color:var(--text-3);">ARR</div>
1840
  </div>
1841
  </div>
1842
  </template>
1843
- <div x-show="sfAccounts.length === 0"
1844
  style="font-size:12px;color:var(--text-3);padding:24px;text-align:center;">No accounts loaded β€” reset to
1845
  start an episode.</div>
1846
  </div>
1847
  </div>
1848
 
1849
- <!-- ======================== WORKDAY ======================== -->
1850
- <div x-show="activeAppTab === 'workday'">
1851
  <div class="wd-header">
1852
  <span class="wd-logo">Workday</span>
1853
  <span class="wd-tagline">People &amp; HR Operations</span>
1854
- <template x-for="[app, ver] in Object.entries(schemaVersions)" :key="app">
1855
- <span x-show="app === 'workday'" class="schema-pill" x-text="'schema ' + ver"></span>
1856
  </template>
1857
  </div>
1858
  <div class="wd-body">
1859
- <!-- Stats -->
1860
  <div class="wd-stat-row">
1861
  <div class="wd-stat">
1862
  <div class="wd-stat-val" x-text="wdEmployees.length"></div>
@@ -1867,32 +1959,30 @@
1867
  <div class="wd-stat-label">Pending Tasks</div>
1868
  </div>
1869
  <div class="wd-stat">
1870
- <div class="wd-stat-val" x-text="slaLogged ? '1' : '0'"></div>
1871
  <div class="wd-stat-label">SLA Events</div>
1872
  </div>
1873
  </div>
1874
-
1875
- <!-- Employee table -->
1876
  <div class="wd-section-title">Employee Records</div>
1877
  <div class="wd-tasks-header">
1878
  <span>Employee</span><span>Department</span><span>Level</span><span>Status</span>
1879
  </div>
1880
  <template x-for="emp in wdEmployees" :key="emp.id">
1881
- <div class="wd-task-row" :class="emp.highlighted ? 'highlighted' : ''">
1882
  <div>
1883
  <div class="wd-emp" x-text="emp.name"></div>
1884
  <div style="font-size:10px;font-family:var(--font-mono);color:var(--text-3);" x-text="emp.id"></div>
1885
  </div>
1886
- <span class="wd-dept" x-text="emp.department || 'β€”'"></span>
1887
- <span class="wd-level" x-text="emp.level || 'β€”'"></span>
1888
  <div>
1889
  <span class="badge"
1890
  :style="emp.status==='active'?'background:#D1FAE5;color:#065F46;':emp.status==='pending'?'background:#FEF3C7;color:#92400E;':'background:var(--bg);color:var(--text-2);'"
1891
- x-text="emp.status || 'active'"></span>
1892
  </div>
1893
  </div>
1894
  </template>
1895
- <div x-show="wdEmployees.length === 0"
1896
  style="font-size:12px;color:var(--text-3);padding:24px;text-align:center;">No employee records β€” reset
1897
  to start an episode.</div>
1898
  </div>
@@ -1901,7 +1991,7 @@
1901
  </div><!-- /app-inner -->
1902
  </div><!-- /app-content -->
1903
 
1904
- <!-- AGENT LOG (bottom strip) -->
1905
  <div class="agent-log">
1906
  <div class="log-header">
1907
  <span class="log-title">Agent Log</span>
@@ -1910,33 +2000,30 @@
1910
  <div class="live-badge-dot live-dot"></div>
1911
  Live
1912
  </div>
1913
- <button @click="actionLog = []"
1914
- style="font-size:11px;color:var(--text-3);cursor:pointer;background:none;border:none;padding:0;">
1915
- Clear
1916
- </button>
1917
  </div>
1918
  </div>
1919
  <div class="log-scroll" id="log-scroll">
1920
- <div x-show="actionLog.length === 0" style="font-size:11px;color:var(--text-3);padding:12px 0;">
1921
- Waiting for episode to start…
1922
- </div>
1923
- <template x-for="(entry, i) in actionLog" :key="i">
1924
  <div class="log-row">
1925
- <span class="log-step" x-text="'#' + entry.step"></span>
1926
  <div class="log-indicator"
1927
  :class="entry.type==='success'?'ind-success':entry.type==='error'?'ind-error':entry.type==='reset'?'ind-reset':'ind-info'">
1928
  </div>
1929
  <div class="log-body">
1930
  <div class="log-tags">
1931
  <template x-if="entry.app">
1932
- <span class="log-app-tag" :class="'tag-' + entry.app" x-text="entry.app"></span>
1933
  </template>
1934
  <template x-if="entry.operation">
1935
- <span class="log-op" x-text="entry.operation + '()'"></span>
1936
  </template>
1937
- <template x-if="entry.reward !== undefined">
1938
- <span class="log-reward" :style="entry.reward >= 0 ? 'color:var(--green)' : 'color:var(--red)'"
1939
- x-text="(entry.reward >= 0 ? '+' : '') + entry.reward.toFixed(4)"></span>
1940
  </template>
1941
  </div>
1942
  <div class="log-msg" x-text="entry.message"></div>
@@ -1948,54 +2035,50 @@
1948
 
1949
  </main>
1950
 
1951
- <!-- RIGHT SIDEBAR: Metrics -->
1952
  <aside class="sidebar-right">
1953
 
1954
- <!-- Score gauge -->
1955
  <div class="score-block">
1956
  <div class="section-label" style="text-align:left;">Episode Score</div>
1957
  <div class="score-gauge-wrap">
1958
  <canvas id="gaugeChart" width="110" height="110"></canvas>
1959
  <div class="score-center">
1960
- <div class="score-val" x-text="(currentScore * 100).toFixed(0)"></div>
1961
  <div class="score-sub">/ 100</div>
1962
  </div>
1963
  </div>
1964
  <div style="font-size:11px;color:var(--text-2);">
1965
- <span x-text="stepCount + ' steps'"></span>
1966
  <span style="margin:0 6px;color:var(--border);">Β·</span>
1967
- <span x-text="maxSteps + ' max'"></span>
1968
  </div>
1969
  </div>
1970
 
1971
- <!-- Reward breakdown -->
1972
  <div class="breakdown-block">
1973
  <div class="section-label">Score Breakdown</div>
1974
  <template x-for="comp in rewardComponents" :key="comp.key">
1975
  <div class="breakdown-row">
1976
  <div class="breakdown-label-row">
1977
  <span class="breakdown-label" x-text="comp.label"></span>
1978
- <span class="breakdown-pct" x-text="(comp.value * 100).toFixed(0) + '%'"></span>
1979
  </div>
1980
  <div class="bar-track">
1981
- <div class="bar-fill" :style="'width:' + (comp.value * 100) + '%;background:' + comp.color"></div>
1982
  </div>
1983
  </div>
1984
  </template>
1985
  </div>
1986
 
1987
- <!-- Reward curve -->
1988
  <div style="padding:14px 16px;border-bottom:1px solid var(--border);flex-shrink:0;">
1989
  <div class="section-label">Reward Per Step</div>
1990
  <canvas id="rewardChart" style="width:100%;max-height:90px;"></canvas>
1991
  </div>
1992
 
1993
- <!-- Episode stats -->
1994
  <div class="stats-block">
1995
  <div class="section-label">Episode Stats</div>
1996
  <div class="stat-row">
1997
  <span class="stat-label">Violations</span>
1998
- <span class="stat-val" :style="violationCount > 0 ? 'color:var(--red)' : 'color:var(--green)'"
1999
  x-text="violationCount"></span>
2000
  </div>
2001
  <div class="stat-row">
@@ -2004,20 +2087,18 @@
2004
  </div>
2005
  <div class="stat-row">
2006
  <span class="stat-label">Schema errors</span>
2007
- <span class="stat-val" :style="schemaErrorCount > 0 ? 'color:var(--red)' : ''"
2008
- x-text="schemaErrorCount"></span>
2009
  </div>
2010
  <div class="stat-row">
2011
  <span class="stat-label">Workflow</span>
2012
- <span class="stat-val" x-text="workflowId || 'β€”'"></span>
2013
  </div>
2014
  </div>
2015
 
2016
- <!-- Violations -->
2017
  <div class="violations-block">
2018
  <div class="section-label">Rule Violations</div>
2019
- <div x-show="violations.length === 0" style="font-size:11px;color:var(--text-3);">None this episode.</div>
2020
- <template x-for="(v, i) in violations.slice(-10)" :key="i">
2021
  <div class="violation-item" x-text="v"></div>
2022
  </template>
2023
  </div>
@@ -2055,10 +2136,12 @@
2055
  { id: 'workday', label: 'Workday', color: '#FF6B35' },
2056
  ],
2057
  activeAppTab: 'zendesk',
2058
- appStates: { zendesk: '', jira: '', salesforce: '', workday: '' },
2059
- appOpenCounts: { zendesk: 0, jira: 0, salesforce: 0, workday: 0 },
2060
 
2061
- // Structured app data for rich UIs
 
 
 
 
2062
  jiraCards: [],
2063
  zdTickets: [],
2064
  sfAccounts: [],
@@ -2084,6 +2167,7 @@
2084
  violations: [],
2085
  actionLog: [],
2086
 
 
2087
  async init() {
2088
  await this.checkHealth();
2089
  _chartInst = this._initRewardChart();
@@ -2107,13 +2191,7 @@
2107
  body: JSON.stringify({ workflow_id: this.selectedWorkflow }),
2108
  });
2109
  const data = await r.json();
2110
- this.actionLog = [];
2111
- this.rewardHistory = [];
2112
- this.violationCount = 0;
2113
- this.schemaAdaptCount = 0;
2114
- this.schemaErrorCount = 0;
2115
- this.violations = [];
2116
- this.slaLogged = false;
2117
  this._applyObservation(data.observation, null, 0);
2118
  this._updateChart();
2119
  this._updateGauge();
@@ -2126,54 +2204,104 @@
2126
  }
2127
  },
2128
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2129
  startAgent() {
2130
  if (this.isRunning) return;
2131
  this.isRunning = true;
 
2132
  const url = `${this.envUrl}/ui/run-agent?workflow_id=${this.selectedWorkflow}`;
2133
  _sseInst = new EventSource(url);
 
2134
  _sseInst.onmessage = (e) => {
2135
  try {
2136
  const evt = JSON.parse(e.data);
2137
  this._handleSSEEvent(evt);
2138
  } catch { }
2139
  };
 
2140
  _sseInst.onerror = () => {
 
 
2141
  this.isRunning = false;
2142
- if (_sseInst) { _sseInst.close(); _sseInst = null; }
 
2143
  this._pushLog({ type: 'error', step: this.stepCount, message: 'SSE connection error.' });
2144
  };
2145
  },
2146
 
 
2147
  stopAgent() {
2148
  this.isRunning = false;
2149
- if (_sseInst) { _sseInst.close(); _sseInst = null; }
 
 
2150
  },
2151
 
 
 
 
2152
  _handleSSEEvent(evt) {
2153
  if (evt.type === 'reset') {
2154
- this.actionLog = [];
2155
- this.rewardHistory = [];
2156
- this.violationCount = 0;
2157
- this.violations = [];
2158
- this.schemaAdaptCount = 0;
2159
- this.schemaErrorCount = 0;
2160
- this.slaLogged = false;
2161
  this._applyObservation(evt.observation, null, 0);
2162
  this._pushLog({ type: 'reset', step: 0, message: `Episode started β€” Workflow ${evt.workflow_id}` });
 
2163
  } else if (evt.type === 'step') {
2164
  const obs = evt.observation;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2165
  this._applyObservation(obs, evt.action, evt.reward);
2166
- if (obs.message && obs.message.includes('Stale schema')) this.schemaErrorCount++;
2167
- if (obs.message && obs.message.includes('SLA event logged')) this.slaLogged = true;
 
 
 
 
 
 
 
 
 
 
2168
  this.rewardHistory.push(evt.reward);
2169
  this._updateChart();
2170
  this._updateGauge();
 
2171
  if (obs.rule_violations && obs.rule_violations.length > 0) {
2172
  this.violations.push(...obs.rule_violations);
2173
  this.violationCount += obs.rule_violations.length;
2174
  }
2175
- // Highlight touched app card
 
2176
  this._highlightAppCard(evt.action, obs.message);
 
2177
  this._pushLog({
2178
  type: evt.reward < 0 ? 'error' : (evt.reward > 0.05 ? 'success' : 'info'),
2179
  step: evt.step,
@@ -2182,122 +2310,154 @@
2182
  reward: evt.reward,
2183
  message: obs.message,
2184
  });
2185
- if (evt.done) this.isRunning = false;
 
 
 
 
 
 
 
 
 
 
2186
  } else if (evt.type === 'done') {
 
 
 
 
 
2187
  this._pushLog({
2188
- type: 'info', step: evt.steps,
2189
- message: `Episode complete. Final score: ${(evt.final_score || 0).toFixed(3)} | Workflow: ${evt.completed ? 'completed' : 'incomplete'}`,
 
2190
  });
2191
- this.stopAgent();
 
 
2192
  } else if (evt.type === 'error') {
2193
- this._pushLog({ type: 'error', step: evt.step || this.stepCount, message: evt.message });
2194
- this.stopAgent();
 
 
 
2195
  }
2196
  },
2197
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2198
  _highlightAppCard(action, message) {
2199
  if (!action) return;
2200
  const app = action.app;
2201
  const args = action.args || {};
2202
 
2203
- // Clear previous highlights
2204
  this.jiraCards = this.jiraCards.map(c => ({ ...c, highlighted: false }));
2205
  this.zdTickets = this.zdTickets.map(t => ({ ...t, highlighted: false }));
2206
  this.sfAccounts = this.sfAccounts.map(a => ({ ...a, highlighted: false }));
2207
  this.wdEmployees = this.wdEmployees.map(e => ({ ...e, highlighted: false }));
2208
 
2209
  if (app === 'jira') {
2210
- this.activeAppTab = 'jira';
2211
  const id = args.issue_id || 'JIRA-001';
2212
  this.jiraCards = this.jiraCards.map(c => ({ ...c, highlighted: c.id === id }));
2213
- // If a new issue was created, parse from message
2214
- if (action.operation === 'create_issue' && message && message.includes('Created JIRA-')) {
2215
  const m = message.match(/Created (JIRA-\d+)/);
2216
- if (m) {
2217
- const newId = m[1];
2218
- if (!this.jiraCards.find(c => c.id === newId)) {
2219
- this.jiraCards = [{
2220
- id: newId, title: args.title || 'New Issue',
2221
- priority: args.priority || args.severity || 'p1',
2222
- assignee: args.assignee || args.owner || null,
2223
- status: 'open',
2224
- linked_zendesk: args.linked_zendesk || args.zendesk_ticket || null,
2225
- highlighted: true,
2226
- }, ...this.jiraCards];
2227
- }
2228
  }
2229
  }
2230
  if (action.operation === 'assign_owner' || action.operation === 'update_status') {
2231
- this.jiraCards = this.jiraCards.map(c => {
2232
- if (c.id === id) {
2233
- return {
2234
- ...c,
2235
- assignee: args.assignee || args.owner || args.assigned_to || c.assignee,
2236
- status: args.status || args.state || args.current_state || c.status,
2237
- highlighted: true,
2238
- };
2239
- }
2240
- return c;
2241
  });
2242
  }
2243
  if (action.operation === 'link_zendesk_ticket') {
2244
- this.jiraCards = this.jiraCards.map(c => c.id === id ? { ...c, linked_zendesk: args.zendesk_ticket_number, highlighted: true } : c);
2245
  }
2246
  }
2247
 
2248
  if (app === 'zendesk') {
2249
- this.activeAppTab = 'zendesk';
2250
  const id = args.ticket_number || 'ZD-001';
2251
  this.zdTickets = this.zdTickets.map(t => ({ ...t, highlighted: t.id === id }));
2252
  if (action.operation === 'acknowledge_ticket') {
2253
- this.zdTickets = this.zdTickets.map(t => t.id === id ? { ...t, state: 'open', highlighted: true } : t);
2254
  }
2255
  if (action.operation === 'assign_agent') {
2256
- this.zdTickets = this.zdTickets.map(t => t.id === id ? { ...t, agent: args.agent_email || args.handler || args.assigned_agent, highlighted: true } : t);
 
2257
  }
2258
  if (action.operation === 'resolve_ticket') {
2259
- this.zdTickets = this.zdTickets.map(t => t.id === id ? { ...t, state: 'resolved', highlighted: true } : t);
2260
  }
2261
  }
2262
 
2263
  if (app === 'salesforce') {
2264
- this.activeAppTab = 'salesforce';
2265
  const id = args.account_id || 'ACME-001';
2266
  this.sfAccounts = this.sfAccounts.map(a => ({ ...a, highlighted: a.id === id }));
2267
  if (action.operation === 'flag_churn_risk') {
2268
- this.sfAccounts = this.sfAccounts.map(a => a.id === id ? { ...a, health: 'red', churn_risk: true, highlighted: true } : a);
2269
  }
2270
  if (action.operation === 'assign_account_owner') {
2271
  const owner = args.owner || args.owner_name || args.account_owner || args.rep_email;
2272
- this.sfAccounts = this.sfAccounts.map(a => a.id === id ? { ...a, owner, highlighted: true } : a);
2273
  }
2274
  }
2275
 
2276
  if (app === 'workday') {
2277
- this.activeAppTab = 'workday';
2278
  const id = args.employee_id || 'EMP-001';
2279
  this.wdEmployees = this.wdEmployees.map(e => ({ ...e, highlighted: e.id === id }));
2280
- if (action.operation === 'create_onboarding_task') {
2281
- if (!this.wdEmployees.find(e => e.id === id)) {
2282
- this.wdEmployees = [{
2283
- id, name: args.name || id,
2284
- department: args.department || 'support',
2285
- level: args.level || args.job_level || args.seniority || 'IC1',
2286
- status: 'pending', highlighted: true,
2287
- }, ...this.wdEmployees];
2288
- }
2289
  }
2290
  if (action.operation === 'log_sla_event') this.slaLogged = true;
2291
  }
2292
  },
2293
 
 
2294
  _applyObservation(obs, action, reward) {
2295
  this.workflowId = obs.workflow_id || '';
2296
  this.workflowGoal = obs.workflow_goal || '';
2297
  this.schemaHints = obs.schema_hints || {};
2298
  this.activeRules = obs.active_rules || {};
2299
  this.stepCount = obs.step_count || 0;
2300
- this.completedSteps = (obs.completed_steps || []).map(id => id);
2301
  this.policyDriftActive = obs.policy_drift_active || false;
2302
 
2303
  const newScore = obs.current_score || 0.001;
@@ -2336,41 +2496,34 @@
2336
  const rb = obs.reward_breakdown || {};
2337
  this.rewardComponents.forEach(c => { c.value = rb[c.key] ?? 0; });
2338
 
2339
- // Parse app states into structured data
2340
  if (obs.app_states) this._parseAppStates(obs.app_states);
2341
  },
2342
 
 
2343
  _parseAppStates(states) {
2344
- // Parse the string representation from get_state_view()
2345
- // into structured objects for our rich UIs.
2346
- // We do a best-effort parse of the dict-string format.
2347
  const parseDictStr = (str) => {
2348
  if (!str) return [];
2349
  return str.split('\n').filter(Boolean).map(line => {
2350
  try {
2351
- // Convert Python dict string to JSON-parseable
2352
- const json = line
2353
- .replace(/'/g, '"')
2354
- .replace(/True/g, 'true')
2355
- .replace(/False/g, 'false')
2356
- .replace(/None/g, 'null');
2357
- return JSON.parse(json);
2358
  } catch { return null; }
2359
  }).filter(Boolean);
2360
  };
2361
 
2362
- // Jira
2363
  const jiraRaw = parseDictStr(states.jira || '');
2364
  if (jiraRaw.length > 0 || this.jiraCards.length === 0) {
2365
- const existing = new Map(this.jiraCards.map(c => [c.id, c]));
2366
  jiraRaw.forEach(r => {
2367
- const id = r.issue_id || r.id;
2368
- if (!id) return;
2369
- const ex = existing.get(id) || {};
2370
- existing.set(id, {
2371
- ...ex,
2372
- id,
2373
- title: r.title || ex.title || 'Untitled issue',
2374
  priority: r.priority || r.severity || r.urgency_level || ex.priority || 'p2',
2375
  assignee: r.assignee || r.owner || r.assigned_to || ex.assignee || null,
2376
  status: r.status || r.state || r.current_state || ex.status || 'open',
@@ -2378,18 +2531,16 @@
2378
  highlighted: ex.highlighted || false,
2379
  });
2380
  });
2381
- this.jiraCards = Array.from(existing.values());
2382
  }
2383
 
2384
- // Zendesk
2385
  const zdRaw = parseDictStr(states.zendesk || '');
2386
  if (zdRaw.length > 0 || this.zdTickets.length === 0) {
2387
- const existing = new Map(this.zdTickets.map(t => [t.id, t]));
2388
  zdRaw.forEach(r => {
2389
- const id = r.ticket_number || r.id;
2390
- if (!id) return;
2391
- const ex = existing.get(id) || {};
2392
- existing.set(id, {
2393
  ...ex, id,
2394
  subject: r.title || ex.subject || 'Support ticket',
2395
  urgency: r.urgency || r.priority || r.impact_level || ex.urgency || 'p2',
@@ -2399,18 +2550,16 @@
2399
  highlighted: ex.highlighted || false,
2400
  });
2401
  });
2402
- this.zdTickets = Array.from(existing.values());
2403
  }
2404
 
2405
- // Salesforce
2406
  const sfRaw = parseDictStr(states.salesforce || '');
2407
  if (sfRaw.length > 0 || this.sfAccounts.length === 0) {
2408
- const existing = new Map(this.sfAccounts.map(a => [a.id, a]));
2409
  sfRaw.forEach(r => {
2410
- const id = r.account_id || r.id;
2411
- if (!id) return;
2412
- const ex = existing.get(id) || {};
2413
- existing.set(id, {
2414
  ...ex, id,
2415
  name: r.company_name || ex.name || id,
2416
  health: r.health || r.account_health || r.risk_score || ex.health || 'green',
@@ -2421,18 +2570,16 @@
2421
  highlighted: ex.highlighted || false,
2422
  });
2423
  });
2424
- this.sfAccounts = Array.from(existing.values());
2425
  }
2426
 
2427
- // Workday
2428
  const wdRaw = parseDictStr(states.workday || '');
2429
  if (wdRaw.length > 0 || this.wdEmployees.length === 0) {
2430
- const existing = new Map(this.wdEmployees.map(e => [e.id, e]));
2431
  wdRaw.forEach(r => {
2432
- const id = r.employee_id || r.id;
2433
- if (!id) return;
2434
- const ex = existing.get(id) || {};
2435
- existing.set(id, {
2436
  ...ex, id,
2437
  name: r.name || ex.name || id,
2438
  department: r.department || ex.department || 'β€”',
@@ -2441,10 +2588,11 @@
2441
  highlighted: ex.highlighted || false,
2442
  });
2443
  });
2444
- this.wdEmployees = Array.from(existing.values());
2445
  }
2446
  },
2447
 
 
2448
  _pushLog(entry) {
2449
  this.actionLog.push(entry);
2450
  this.$nextTick(() => {
 
7
  <title>OrgOS β€” Enterprise RL Environment</title>
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
9
  <link
10
+ href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,300;0,9..40,400;0,9..40,500;0,9..40,600;1,9..40,400&family=DM+Mono:wght@400;500&display=swap"
11
  rel="stylesheet">
12
  <script src="https://cdn.tailwindcss.com"></script>
13
  <script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
 
17
  *::before,
18
  *::after {
19
  box-sizing: border-box;
20
+ margin: 0;
21
+ padding: 0;
22
  }
23
 
24
  :root {
25
  --font-sans: 'DM Sans', sans-serif;
26
  --font-mono: 'DM Mono', monospace;
 
 
27
  --bg: #F5F6F8;
28
  --surface: #FFFFFF;
29
  --border: #E3E6EA;
30
  --text-1: #111827;
31
  --text-2: #6B7280;
32
  --text-3: #9CA3AF;
33
+ --green: #10B981;
34
+ --red: #EF4444;
35
+ --amber: #F59E0B;
36
  --jira: #0052CC;
37
  --jira-light: #E9F0FF;
38
  --jira-mid: #B3C8F0;
 
39
  --zendesk: #03363D;
40
  --zendesk-light: #E6F3F4;
41
  --zendesk-mid: #B2D8DB;
 
42
  --sf: #00A1E0;
43
  --sf-light: #E5F6FD;
44
  --sf-mid: #B3E3F8;
 
45
  --wd: #FF6B35;
46
  --wd-light: #FFF0EB;
47
  --wd-mid: #FFD0C0;
 
 
 
 
 
 
48
  }
49
 
50
  body {
 
55
  line-height: 1.5;
56
  }
57
 
 
58
  ::-webkit-scrollbar {
59
  width: 5px;
60
  height: 5px;
 
73
  background: var(--text-3);
74
  }
75
 
76
+ /* ── Animations ── */
77
  @keyframes fadeUp {
78
  from {
79
  opacity: 0;
 
94
  }
95
 
96
  50% {
97
+ opacity: 0.35;
98
  }
99
  }
100
 
101
+ @keyframes checkmark {
102
  from {
103
+ transform: scale(0) rotate(-20deg);
104
  opacity: 0;
 
105
  }
106
 
107
  to {
108
+ transform: scale(1) rotate(0);
109
  opacity: 1;
 
110
  }
111
  }
112
 
113
+ @keyframes score-flash {
114
+
115
+ 0%,
116
+ 100% {
117
+ background: transparent;
118
+ }
119
+
120
+ 40% {
121
+ background: rgba(16, 185, 129, 0.12);
122
+ }
123
+ }
124
+
125
+ @keyframes record-focus {
126
+ 0% {
127
+ transform: scale(0.985);
128
+ }
129
+
130
+ 45% {
131
+ transform: scale(1.018);
132
+ }
133
+
134
+ 100% {
135
+ transform: scale(1);
136
+ }
137
+ }
138
+
139
+ @keyframes action-sweep {
140
+ 0% {
141
+ transform: translateX(-100%);
142
+ }
143
+
144
+ 100% {
145
+ transform: translateX(250%);
146
+ }
147
+ }
148
+
149
+ @keyframes banner-in {
150
  from {
 
151
  opacity: 0;
152
+ transform: translateY(-8px);
153
  }
154
 
155
  to {
 
156
  opacity: 1;
157
+ transform: translateY(0);
158
  }
159
  }
160
 
161
+ @keyframes tab-pulse {
162
 
163
  0%,
164
  100% {
165
+ opacity: 1;
166
  }
167
 
168
+ 50% {
169
+ opacity: 0.6;
170
  }
171
  }
172
 
 
174
  animation: fadeUp 0.25s ease forwards;
175
  }
176
 
 
 
 
 
177
  .live-dot {
178
  animation: pulse-dot 1.4s ease-in-out infinite;
179
  }
 
186
  animation: score-flash 0.5s ease;
187
  }
188
 
189
+ .record-focus {
190
+ animation: record-focus 0.75s ease;
191
+ }
192
+
193
  [x-cloak] {
194
  display: none !important;
195
  }
196
 
197
+ /* ── Topbar ── */
198
  .topbar {
199
  background: var(--surface);
200
  border-bottom: 1px solid var(--border);
 
202
  display: flex;
203
  align-items: center;
204
  padding: 0 20px;
205
+ gap: 14px;
206
  position: sticky;
207
  top: 0;
208
  z-index: 100;
 
233
  .logo-sub {
234
  font-size: 11px;
235
  color: var(--text-3);
 
236
  }
237
 
238
  .divider {
 
246
  background: var(--bg);
247
  border: 1px solid var(--border);
248
  border-radius: 7px;
249
+ padding: 5px 28px 5px 10px;
250
  font-family: var(--font-sans);
251
  font-size: 12px;
252
  color: var(--text-1);
 
254
  outline: none;
255
  cursor: pointer;
256
  appearance: none;
 
257
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%236B7280' viewBox='0 0 16 16'%3E%3Cpath d='M7.247 11.14 2.451 5.658C1.885 5.013 2.345 4 3.204 4h9.592a1 1 0 0 1 .753 1.659l-4.796 5.48a1 1 0 0 1-1.506 0z'/%3E%3C/svg%3E");
258
  background-repeat: no-repeat;
259
  background-position: right 9px center;
 
 
 
 
 
260
  }
261
 
262
  .btn {
 
316
  font-weight: 500;
317
  }
318
 
319
+ /* ── Layout ── */
320
  .layout {
321
  display: grid;
322
  grid-template-columns: 260px 1fr 240px;
 
324
  overflow: hidden;
325
  }
326
 
327
+ /* ── Left sidebar ── */
328
  .sidebar-left {
329
  background: var(--surface);
330
  border-right: 1px solid var(--border);
 
348
  margin-bottom: 10px;
349
  }
350
 
 
351
  .step-row {
352
  display: flex;
353
  align-items: flex-start;
 
388
  }
389
 
390
  .step-desc.done {
391
+ color: var(--green);
392
+ font-weight: 500;
393
  }
394
 
 
395
  .drift-pill {
396
  display: flex;
397
  align-items: center;
 
427
  margin-left: auto;
428
  }
429
 
 
430
  .rule-row {
431
  display: flex;
432
  justify-content: space-between;
 
446
  color: var(--text-1);
447
  }
448
 
449
+ /* ── Center panel ── */
450
  .center-panel {
451
  display: flex;
452
  flex-direction: column;
 
454
  background: var(--bg);
455
  }
456
 
457
+ /* ── Action banner (cinematic) ── */
458
+ .action-banner {
459
+ margin: 10px 14px 0;
460
+ background: var(--surface);
461
+ border: 1px solid var(--border);
462
+ border-radius: 10px;
463
+ padding: 9px 14px;
464
+ display: grid;
465
+ grid-template-columns: auto 1fr auto;
466
+ gap: 12px;
467
+ align-items: center;
468
+ position: relative;
469
+ overflow: hidden;
470
+ box-shadow: 0 4px 16px rgba(17, 24, 39, 0.07);
471
+ animation: banner-in 0.2s ease;
472
+ flex-shrink: 0;
473
+ }
474
+
475
+ .action-banner::after {
476
+ content: '';
477
+ position: absolute;
478
+ top: 0;
479
+ left: -60%;
480
+ width: 40%;
481
+ height: 100%;
482
+ background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.65), transparent);
483
+ animation: action-sweep 1.4s ease-in-out infinite;
484
+ pointer-events: none;
485
+ }
486
+
487
+ .banner-dot {
488
+ width: 36px;
489
+ height: 36px;
490
+ border-radius: 9px;
491
+ display: flex;
492
+ align-items: center;
493
+ justify-content: center;
494
+ color: #fff;
495
+ font-weight: 700;
496
+ font-size: 11px;
497
+ position: relative;
498
+ z-index: 1;
499
+ flex-shrink: 0;
500
+ }
501
+
502
+ .banner-copy {
503
+ position: relative;
504
+ z-index: 1;
505
+ min-width: 0;
506
+ }
507
+
508
+ .banner-eyebrow {
509
+ font-size: 10px;
510
+ color: var(--text-3);
511
+ font-weight: 600;
512
+ text-transform: uppercase;
513
+ letter-spacing: 0.08em;
514
+ margin-bottom: 2px;
515
+ }
516
+
517
+ .banner-title {
518
+ font-size: 13px;
519
+ font-weight: 600;
520
+ color: var(--text-1);
521
+ white-space: nowrap;
522
+ overflow: hidden;
523
+ text-overflow: ellipsis;
524
+ }
525
+
526
+ .banner-sub {
527
+ font-size: 11px;
528
+ color: var(--text-2);
529
+ white-space: nowrap;
530
+ overflow: hidden;
531
+ text-overflow: ellipsis;
532
+ }
533
+
534
+ .banner-step {
535
+ position: relative;
536
+ z-index: 1;
537
+ font-family: var(--font-mono);
538
+ font-size: 11px;
539
+ color: var(--text-3);
540
+ background: var(--bg);
541
+ border-radius: 99px;
542
+ padding: 3px 9px;
543
+ flex-shrink: 0;
544
+ }
545
+
546
+ .banner-jira {
547
+ border-color: rgba(0, 82, 204, 0.25);
548
+ }
549
+
550
+ .banner-zendesk {
551
+ border-color: rgba(3, 54, 61, 0.25);
552
+ }
553
+
554
+ .banner-salesforce {
555
+ border-color: rgba(0, 161, 224, 0.25);
556
+ }
557
+
558
+ .banner-workday {
559
+ border-color: rgba(255, 107, 53, 0.25);
560
+ }
561
+
562
+ /* ── App tabs ── */
563
  .app-tabs {
564
  display: flex;
565
  background: var(--surface);
 
592
  border-bottom-color: var(--text-1);
593
  }
594
 
595
+ .app-tab.acting {
596
+ animation: tab-pulse 0.8s ease-in-out 2;
597
+ }
598
+
599
  .app-tab .app-dot {
600
  width: 8px;
601
  height: 8px;
 
617
  color: #fff;
618
  }
619
 
620
+ /* ── App content ── */
621
+ .app-content {
622
+ flex: 1;
623
+ overflow: hidden;
624
+ display: flex;
625
+ flex-direction: column;
626
+ }
627
+
628
+ .app-inner {
629
+ flex: 1;
630
+ overflow-y: auto;
631
+ }
632
+
633
+ /* ── JIRA ── */
634
  .jira-header {
635
  background: var(--jira);
636
  padding: 10px 20px;
 
647
  }
648
 
649
  .jira-project {
650
+ font-size: 11px;
651
  color: rgba(255, 255, 255, 0.7);
652
+ background: rgba(255, 255, 255, 0.15);
653
+ border-radius: 4px;
654
+ padding: 2px 8px;
655
  }
656
 
657
  .jira-nav {
 
666
  font-size: 12px;
667
  color: rgba(255, 255, 255, 0.8);
668
  cursor: pointer;
 
669
  }
670
 
671
  .jira-nav-item:hover {
 
718
  margin-bottom: 6px;
719
  border: 1px solid var(--border);
720
  cursor: pointer;
721
+ transition: box-shadow 0.15s, border-color 0.2s, transform 0.2s;
722
  }
723
 
724
  .jira-card:hover {
 
727
 
728
  .jira-card.highlighted {
729
  border-color: var(--jira);
730
+ box-shadow: 0 0 0 3px rgba(0, 82, 204, 0.18), 0 12px 30px rgba(0, 82, 204, 0.16);
731
+ animation: record-focus 0.75s ease;
732
  }
733
 
734
  .jira-card-title {
 
783
  color: var(--jira);
784
  }
785
 
786
+ /* ── ZENDESK ── */
787
  .zd-header {
788
  background: var(--zendesk);
789
  padding: 10px 20px;
 
885
  margin-bottom: 4px;
886
  font-size: 12px;
887
  cursor: pointer;
888
+ transition: box-shadow 0.15s, border-color 0.2s, transform 0.2s;
889
  }
890
 
891
  .zd-ticket-row:hover {
 
894
 
895
  .zd-ticket-row.highlighted {
896
  border-color: var(--zendesk);
897
+ box-shadow: 0 0 0 3px rgba(3, 54, 61, 0.16), 0 12px 30px rgba(3, 54, 61, 0.12);
898
+ animation: record-focus 0.75s ease;
899
  }
900
 
901
  .zd-tid {
 
909
  color: var(--text-1);
910
  }
911
 
912
+ .zd-urgency,
913
+ .zd-status {
914
  text-align: center;
915
  }
916
 
 
943
  color: var(--text-2);
944
  }
945
 
 
 
 
 
946
  .status-badge {
947
  display: inline-block;
948
  width: 8px;
 
966
  background: var(--red);
967
  }
968
 
969
+ /* ── SALESFORCE ── */
970
  .sf-header {
971
  background: var(--sf);
972
  padding: 10px 20px;
 
1019
 
1020
  .sf-stages {
1021
  display: flex;
 
1022
  margin-bottom: 16px;
1023
  border-radius: 6px;
1024
  overflow: hidden;
 
1035
  text-transform: uppercase;
1036
  letter-spacing: 0.05em;
1037
  color: var(--sf);
 
1038
  }
1039
 
1040
  .sf-stage.active {
 
1057
  gap: 12px;
1058
  align-items: center;
1059
  cursor: pointer;
1060
+ transition: box-shadow 0.15s, border-color 0.2s, transform 0.2s;
1061
  }
1062
 
1063
  .sf-account-card:hover {
 
1066
 
1067
  .sf-account-card.highlighted {
1068
  border-color: var(--sf);
1069
+ box-shadow: 0 0 0 3px rgba(0, 161, 224, 0.18), 0 12px 30px rgba(0, 161, 224, 0.16);
1070
+ animation: record-focus 0.75s ease;
1071
  }
1072
 
1073
  .sf-avatar {
 
1122
  color: var(--text-1);
1123
  }
1124
 
1125
+ /* ── WORKDAY ── */
1126
  .wd-header {
1127
  background: var(--wd);
1128
  padding: 10px 20px;
 
1178
  font-size: 12px;
1179
  align-items: center;
1180
  cursor: pointer;
1181
+ transition: box-shadow 0.15s, border-color 0.2s, transform 0.2s;
1182
  }
1183
 
1184
  .wd-task-row:hover {
 
1187
 
1188
  .wd-task-row.highlighted {
1189
  border-color: var(--wd);
1190
+ box-shadow: 0 0 0 3px rgba(255, 107, 53, 0.18), 0 12px 30px rgba(255, 107, 53, 0.14);
1191
+ animation: record-focus 0.75s ease;
1192
  }
1193
 
1194
  .wd-emp {
 
1233
  margin-top: 2px;
1234
  }
1235
 
1236
+ /* ── Agent log ── */
1237
  .agent-log {
 
1238
  display: flex;
1239
  flex-direction: column;
1240
  background: var(--surface);
1241
  border-top: 1px solid var(--border);
1242
  overflow: hidden;
1243
+ min-height: 180px;
1244
+ max-height: 260px;
1245
  }
1246
 
1247
  .log-header {
 
1372
  line-height: 1.4;
1373
  }
1374
 
1375
+ /* ── Right sidebar ── */
1376
  .sidebar-right {
1377
  background: var(--surface);
1378
  border-left: 1px solid var(--border);
 
1381
  overflow: hidden;
1382
  }
1383
 
 
1384
  .score-block {
1385
  padding: 20px 16px 16px;
1386
  border-bottom: 1px solid var(--border);
 
1422
  margin-top: 2px;
1423
  }
1424
 
 
1425
  .breakdown-block {
1426
  padding: 14px 16px;
1427
  border-bottom: 1px solid var(--border);
 
1462
  transition: width 0.4s ease;
1463
  }
1464
 
 
1465
  .stats-block {
1466
  padding: 14px 16px;
1467
  border-bottom: 1px solid var(--border);
 
1487
  color: var(--text-1);
1488
  }
1489
 
 
1490
  .violations-block {
1491
  padding: 14px 16px;
1492
  flex: 1;
 
1505
  border-bottom: none;
1506
  }
1507
 
1508
+ /* ── Misc ── */
1509
  .drift-banner {
1510
  background: linear-gradient(90deg, #FFFBEB, #FEF3C7);
1511
  border: 1px solid #FDE68A;
1512
  border-radius: 7px;
1513
  padding: 8px 12px;
1514
+ margin: 10px 14px 0;
1515
  display: flex;
1516
  align-items: center;
1517
  gap: 8px;
1518
+ animation: banner-in 0.3s ease;
1519
+ flex-shrink: 0;
1520
  }
1521
 
1522
  .drift-banner-dot {
 
1533
  color: #92400E;
1534
  }
1535
 
1536
+ .schema-pill {
1537
+ background: rgba(255, 255, 255, 0.18);
1538
+ border-radius: 4px;
1539
+ padding: 2px 8px;
1540
+ font-size: 10px;
1541
+ color: rgba(255, 255, 255, 0.9);
1542
+ font-family: var(--font-mono);
 
 
 
 
1543
  }
1544
 
 
1545
  .live-badge {
1546
  display: flex;
1547
  align-items: center;
 
1557
  border-radius: 50%;
1558
  background: var(--green);
1559
  }
 
 
 
 
 
 
 
 
 
 
1560
  </style>
1561
  </head>
1562
 
1563
  <body x-data="orgos()" x-init="init()">
1564
 
1565
+ <!-- ═══ TOP BAR ═══ -->
1566
  <header class="topbar">
1567
  <div class="logo-mark">OS</div>
1568
  <div>
1569
  <div class="logo-text">OrgOS</div>
1570
  </div>
1571
  <div class="logo-sub">Enterprise RL Environment</div>
 
1572
  <div class="divider"></div>
 
1573
  <label style="font-size:11px;color:var(--text-3);font-weight:500;">Workflow</label>
1574
  <select class="wf-select" x-model="selectedWorkflow">
1575
  <option value="A">A β€” Customer Bug Fix</option>
1576
  <option value="B">B β€” Employee Onboarding</option>
1577
  <option value="C">C β€” Churn Risk Alert</option>
1578
  </select>
 
1579
  <button class="btn btn-ghost" @click="resetEpisode()" :disabled="isRunning">Reset</button>
 
1580
  <button class="btn" :class="isRunning ? 'btn-danger' : 'btn-primary'"
1581
  @click="isRunning ? stopAgent() : startAgent()">
1582
  <svg x-show="!isRunning" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
 
1589
  </svg>
1590
  <span x-text="isRunning ? 'Stop' : 'Run Agent'"></span>
1591
  </button>
 
1592
  <div class="divider"></div>
 
 
1593
  <div style="display:flex;align-items:baseline;gap:5px;">
1594
  <span style="font-size:11px;color:var(--text-3);font-weight:500;">Score</span>
1595
  <span style="font-size:18px;font-weight:600;color:var(--text-1);font-family:var(--font-mono);"
1596
  :class="scoreUpdated ? 'score-flash' : ''" x-text="currentScore.toFixed(3)"></span>
1597
  </div>
 
1598
  <div style="display:flex;align-items:baseline;gap:5px;">
1599
  <span style="font-size:11px;color:var(--text-3);font-weight:500;">Step</span>
1600
  <span style="font-size:18px;font-weight:600;color:var(--text-1);font-family:var(--font-mono);"
1601
+ x-text="stepCount+'/'+maxSteps"></span>
1602
  </div>
 
1603
  <div x-show="policyDriftActive" class="badge" style="background:#FEF3C7;color:#92400E;border:1px solid #FDE68A;">
1604
+ Policy Drift Active</div>
1605
+ <div style="margin-left:auto;display:flex;align-items:center;gap:6px;">
1606
+ <div class="live-badge-dot" :class="serverHealthy?'live-dot':''"
1607
+ :style="serverHealthy?'':'background:var(--red)'"></div>
1608
+ <span style="font-size:11px;" :class="serverHealthy?'live-badge':''"
1609
+ :style="!serverHealthy?'color:var(--red);font-size:11px;':''"
1610
+ x-text="serverHealthy?'Connected':'Offline'"></span>
 
 
1611
  </div>
1612
  </header>
1613
 
1614
+ <!-- ═══ THREE-COLUMN LAYOUT ═══ -->
1615
  <div class="layout">
1616
 
1617
+ <!-- ── LEFT SIDEBAR ── -->
1618
  <aside class="sidebar-left">
1619
 
 
1620
  <div class="sidebar-section" style="flex:1;overflow-y:auto;">
1621
  <div class="section-label">
1622
+ Workflow <span style="color:var(--text-1);font-weight:700;" x-text="workflowId||selectedWorkflow"></span>
 
1623
  Progress
1624
  </div>
1625
  <div style="margin-bottom:12px;">
1626
  <div style="display:flex;justify-content:space-between;font-size:11px;color:var(--text-2);margin-bottom:5px;">
1627
+ <span x-text="completedSteps.length+' of '+totalSteps+' steps done'"></span>
1628
  <span style="font-weight:600;color:var(--text-1);"
1629
+ x-text="Math.round(completedSteps.length/Math.max(totalSteps,1)*100)+'%'"></span>
1630
  </div>
1631
  <div style="height:4px;background:var(--bg);border-radius:2px;">
1632
  <div style="height:4px;background:var(--green);border-radius:2px;transition:width 0.4s ease;"
1633
+ :style="'width:'+(completedSteps.length/Math.max(totalSteps,1)*100)+'%'"></div>
1634
  </div>
1635
  </div>
1636
  <div x-show="workflowGoal"
1637
  style="font-size:11px;color:var(--text-2);line-height:1.5;margin-bottom:12px;padding:8px;background:var(--bg);border-radius:6px;"
1638
  x-text="workflowGoal"></div>
1639
+ <template x-for="(step,i) in allSteps" :key="i">
 
1640
  <div class="step-row">
1641
+ <div class="step-num" :class="completedSteps.includes(step.id)?'done':''">
1642
  <template x-if="completedSteps.includes(step.id)">
1643
  <svg class="step-check" width="10" height="10" fill="currentColor" viewBox="0 0 16 16">
1644
  <path
 
1649
  <span x-text="step.id"></span>
1650
  </template>
1651
  </div>
1652
+ <span class="step-desc" :class="completedSteps.includes(step.id)?'done':''"
1653
  x-text="step.description"></span>
1654
  </div>
1655
  </template>
1656
  </div>
1657
 
 
1658
  <div class="sidebar-section">
1659
  <div class="section-label">Schema Drift</div>
1660
+ <div x-show="Object.keys(schemaHints).length===0" style="font-size:11px;color:var(--text-3);">No drift β€” v1
1661
+ canonical names active.</div>
1662
+ <template x-for="[field,drifted] in Object.entries(schemaHints)" :key="field">
 
1663
  <div class="drift-pill">
1664
+ <span class="drift-old" x-text="field.split('.')[1]??field"></span>
1665
  <span class="drift-arr">β†’</span>
1666
  <span class="drift-new" x-text="drifted"></span>
1667
+ <span class="drift-app" x-text="field.split('.')[0]??''"></span>
1668
  </div>
1669
  </template>
1670
  </div>
1671
 
 
1672
  <div class="sidebar-section">
1673
  <div class="section-label">Active Rules</div>
1674
+ <template x-for="[key,val] in Object.entries(activeRules)" :key="key">
1675
  <div class="rule-row">
1676
  <span class="rule-key" x-text="key.replace(/_/g,' ')"></span>
1677
  <span class="rule-val" x-text="val"></span>
1678
  </div>
1679
  </template>
1680
+ <div x-show="Object.keys(activeRules).length===0" style="font-size:11px;color:var(--text-3);">Reset to load
1681
  rules.</div>
1682
  </div>
1683
 
 
1684
  <div class="sidebar-section" style="border-bottom:none;">
1685
  <div class="section-label">Schema Versions</div>
1686
  <div style="display:flex;gap:6px;flex-wrap:wrap;">
1687
+ <template x-for="[app,ver] in Object.entries(schemaVersions)" :key="app">
1688
  <div
1689
  style="background:var(--bg);border:1px solid var(--border);border-radius:5px;padding:3px 8px;font-size:11px;">
1690
  <span style="color:var(--text-3);" x-text="app.charAt(0).toUpperCase()+app.slice(1,3)"></span>
 
1692
  x-text="ver"></span>
1693
  </div>
1694
  </template>
1695
+ <div x-show="Object.keys(schemaVersions).length===0" style="font-size:11px;color:var(--text-3);">β€”</div>
1696
  </div>
1697
  </div>
 
1698
  </aside>
1699
 
1700
+ <!-- ── CENTER PANEL ── -->
1701
  <main class="center-panel">
1702
 
1703
  <!-- Policy drift banner -->
1704
+ <div x-show="policyDriftActive" class="drift-banner">
1705
  <div class="drift-banner-dot live-dot"></div>
1706
  <span class="drift-banner-text">Policy drift active β€” SLA and approval thresholds have tightened this
1707
  episode.</span>
1708
  </div>
1709
 
1710
+ <!-- β˜… CINEMATIC ACTION BANNER β€” shows while agent is acting on an app -->
1711
+ <div x-show="cinematicAction" class="action-banner" :class="cinematicAction?'banner-'+cinematicAction.app:''">
1712
+ <div class="banner-dot" :style="'background:'+appColor(cinematicAction?.app)"
1713
+ x-text="appInitial(cinematicAction?.app)"></div>
1714
+ <div class="banner-copy">
1715
+ <div class="banner-eyebrow">Agent operating in <span x-text="appLabel(cinematicAction?.app)"></span></div>
1716
+ <div class="banner-title" x-text="operationLabel(cinematicAction)"></div>
1717
+ <div class="banner-sub" x-text="cinematicAction?.summary||'Applying workflow action...'"></div>
1718
+ </div>
1719
+ <div class="banner-step" x-text="'step '+(cinematicAction?.step||stepCount)"></div>
1720
+ </div>
1721
+
1722
  <!-- App tab strip -->
1723
+ <div class="app-tabs">
1724
  <template x-for="tab in appTabs" :key="tab.id">
1725
+ <div class="app-tab"
1726
+ :class="[(activeAppTab===tab.id?'active':''), (cinematicAction?.app===tab.id&&activeAppTab===tab.id?'acting':'')]"
1727
+ @click="activeAppTab=tab.id">
1728
+ <div class="app-dot" :style="'background:'+tab.color"></div>
1729
  <span x-text="tab.label"></span>
1730
+ <template x-if="appOpenCounts[tab.id]>0">
1731
  <span class="tab-count" x-text="appOpenCounts[tab.id]"></span>
1732
  </template>
1733
  </div>
 
1738
  <div class="app-content">
1739
  <div class="app-inner">
1740
 
1741
+ <!-- ════ JIRA ════ -->
1742
+ <div x-show="activeAppTab==='jira'">
1743
  <div class="jira-header">
1744
  <span class="jira-logo">Jira</span>
1745
+ <span class="jira-project">OrgOS / Engineering</span>
1746
+ <template x-for="[app,ver] in Object.entries(schemaVersions)" :key="app">
1747
+ <span x-show="app==='jira'" class="schema-pill" x-text="'schema '+ver"></span>
 
 
1748
  </template>
1749
  <div class="jira-nav" style="margin-left:auto;">
1750
  <div class="jira-nav-item">Board</div>
 
1755
  <div class="jira-board">
1756
  <div style="font-size:12px;font-weight:600;color:var(--text-1);margin-bottom:14px;">Sprint Board</div>
1757
  <div class="jira-board-cols">
 
1758
  <div>
1759
+ <div class="jira-col-header">To Do <span class="jira-col-count"
1760
+ x-text="jiraCards.filter(c=>c.status==='open').length"></span></div>
 
 
1761
  <div class="jira-col-body">
1762
  <template x-for="card in jiraCards.filter(c=>c.status==='open')" :key="card.id">
1763
+ <div class="jira-card" :class="card.highlighted?'highlighted':''">
1764
  <div class="jira-card-title" x-text="card.title"></div>
1765
  <div class="jira-card-meta">
1766
+ <div class="priority-dot" :class="card.priority==='p0'?'p0':card.priority==='p1'?'p1':'p2'">
1767
+ </div>
1768
  <span class="jira-id" x-text="card.id"></span>
1769
  <template x-if="card.linked_zendesk">
1770
  <span
1771
  style="font-size:10px;background:var(--zendesk-light);color:var(--zendesk);border-radius:3px;padding:1px 5px;font-weight:500;"
1772
  x-text="card.linked_zendesk"></span>
1773
  </template>
1774
+ <div class="jira-assignee" x-text="card.assignee?card.assignee.charAt(0).toUpperCase():'?'"
1775
+ :style="card.assignee?'':'color:var(--text-3);'"></div>
 
1776
  </div>
1777
  </div>
1778
  </template>
1779
+ <div x-show="jiraCards.filter(c=>c.status==='open').length===0"
1780
  style="font-size:11px;color:var(--text-3);padding:12px;text-align:center;">No open issues</div>
1781
  </div>
1782
  </div>
 
1783
  <div>
1784
+ <div class="jira-col-header">In Progress <span class="jira-col-count"
1785
+ x-text="jiraCards.filter(c=>c.status==='in_progress').length"></span></div>
 
 
1786
  <div class="jira-col-body">
1787
  <template x-for="card in jiraCards.filter(c=>c.status==='in_progress')" :key="card.id">
1788
+ <div class="jira-card" :class="card.highlighted?'highlighted':''">
1789
  <div class="jira-card-title" x-text="card.title"></div>
1790
  <div class="jira-card-meta">
1791
+ <div class="priority-dot" :class="card.priority==='p0'?'p0':card.priority==='p1'?'p1':'p2'">
1792
+ </div>
1793
  <span class="jira-id" x-text="card.id"></span>
1794
+ <div class="jira-assignee" x-text="card.assignee?card.assignee.charAt(0).toUpperCase():'?'">
1795
+ </div>
1796
  </div>
1797
  </div>
1798
  </template>
1799
+ <div x-show="jiraCards.filter(c=>c.status==='in_progress').length===0"
1800
  style="font-size:11px;color:var(--text-3);padding:12px;text-align:center;">Nothing in progress
1801
  </div>
1802
  </div>
1803
  </div>
 
1804
  <div>
1805
+ <div class="jira-col-header" style="color:var(--green);">Done <span class="jira-col-count"
1806
+ x-text="jiraCards.filter(c=>c.status==='closed'||c.status==='done').length"></span></div>
 
 
 
1807
  <div class="jira-col-body">
1808
  <template x-for="card in jiraCards.filter(c=>c.status==='closed'||c.status==='done')"
1809
  :key="card.id">
1810
+ <div class="jira-card" style="opacity:0.65;">
1811
  <div class="jira-card-title" style="text-decoration:line-through;" x-text="card.title"></div>
1812
+ <div class="jira-card-meta"><span class="jira-id" x-text="card.id"></span></div>
 
 
1813
  </div>
1814
  </template>
1815
+ <div x-show="jiraCards.filter(c=>c.status==='closed'||c.status==='done').length===0"
1816
  style="font-size:11px;color:var(--text-3);padding:12px;text-align:center;">Nothing done yet</div>
1817
  </div>
1818
  </div>
 
1820
  </div>
1821
  </div>
1822
 
1823
+ <!-- ════ ZENDESK ════ -->
1824
+ <div x-show="activeAppTab==='zendesk'">
1825
  <div class="zd-header">
1826
  <span class="zd-logo">Zendesk</span>
1827
  <input class="zd-search" placeholder="Search tickets..." readonly />
1828
+ <template x-for="[app,ver] in Object.entries(schemaVersions)" :key="app">
1829
+ <span x-show="app==='zendesk'" class="schema-pill" x-text="'schema '+ver"></span>
1830
  </template>
1831
  <div style="margin-left:auto;display:flex;align-items:center;gap:8px;">
1832
  <div class="badge" style="background:rgba(255,255,255,0.15);color:#fff;font-size:10px;"
1833
+ x-text="zdTickets.filter(t=>t.state==='open'||t.state==='new').length+' open'"></div>
1834
  </div>
1835
  </div>
1836
  <div class="zd-layout" style="height:calc(100% - 42px);">
1837
  <div class="zd-sidebar">
1838
  <div class="zd-nav-group">
1839
  <div class="zd-nav-title">Views</div>
1840
+ <div class="zd-nav-item active">All tickets <span class="zd-count" x-text="zdTickets.length"></span>
 
 
 
 
 
 
 
 
 
 
1841
  </div>
1842
+ <div class="zd-nav-item">Open <span class="zd-count"
1843
+ x-text="zdTickets.filter(t=>t.state==='open').length"></span></div>
1844
+ <div class="zd-nav-item">New <span class="zd-count"
1845
+ x-text="zdTickets.filter(t=>t.state==='new').length"></span></div>
1846
  <div class="zd-nav-item">Pending</div>
1847
  <div class="zd-nav-item">Resolved</div>
1848
  </div>
 
1854
  </div>
1855
  </div>
1856
  <div class="zd-tickets">
 
1857
  <div
1858
  style="display:grid;grid-template-columns:60px 1fr 80px 90px 80px;gap:12px;padding:6px 14px;font-size:10px;font-weight:600;text-transform:uppercase;letter-spacing:0.07em;color:var(--text-3);margin-bottom:4px;">
1859
  <span>ID</span><span>Subject</span><span
 
1861
  style="text-align:center;">Status</span>
1862
  </div>
1863
  <template x-for="ticket in zdTickets" :key="ticket.id">
1864
+ <div class="zd-ticket-row" :class="ticket.highlighted?'highlighted':''">
1865
  <span class="zd-tid" x-text="ticket.id"></span>
1866
  <span class="zd-subject" x-text="ticket.subject"></span>
1867
  <div class="zd-urgency">
1868
  <span class="urgency-badge"
1869
  :class="ticket.urgency==='p0'||ticket.urgency==='high'?'urg-high':ticket.urgency==='p1'||ticket.urgency==='medium'?'urg-medium':'urg-low'"
1870
+ x-text="ticket.urgency||'p2'"></span>
1871
  </div>
1872
+ <span class="zd-agent" x-text="ticket.agent||'β€”'"></span>
1873
+ <div class="zd-status">
1874
  <span class="status-badge"
1875
  :class="ticket.state==='open'?'status-open':ticket.state==='pending'?'status-pending':ticket.state==='resolved'?'status-resolved':'status-new'"></span>
1876
  <span style="font-size:10px;color:var(--text-2);margin-left:4px;"
1877
+ x-text="ticket.state||'new'"></span>
1878
  </div>
1879
  </div>
1880
  </template>
1881
+ <div x-show="zdTickets.length===0"
1882
  style="font-size:12px;color:var(--text-3);padding:24px;text-align:center;">No tickets loaded β€” reset
1883
  to start an episode.</div>
1884
  </div>
1885
  </div>
1886
  </div>
1887
 
1888
+ <!-- ════ SALESFORCE ════ -->
1889
+ <div x-show="activeAppTab==='salesforce'">
1890
  <div class="sf-header">
1891
  <span class="sf-logo">Salesforce</span>
1892
  <div class="sf-tabs">
 
1894
  <div class="sf-tab">Opportunities</div>
1895
  <div class="sf-tab">Pipeline</div>
1896
  </div>
1897
+ <template x-for="[app,ver] in Object.entries(schemaVersions)" :key="app">
1898
+ <span x-show="app==='salesforce'" class="schema-pill" x-text="'schema '+ver"></span>
1899
  </template>
1900
  </div>
1901
  <div class="sf-body">
 
1902
  <div class="sf-pipeline">
1903
  <div class="sf-pipeline-title">Deal Pipeline</div>
1904
  <div class="sf-stages">
 
1909
  <div class="sf-stage">Closed Won</div>
1910
  </div>
1911
  </div>
 
 
1912
  <div style="font-size:12px;font-weight:600;color:var(--text-1);margin-bottom:10px;">Accounts</div>
1913
  <template x-for="acct in sfAccounts" :key="acct.id">
1914
+ <div class="sf-account-card" :class="acct.highlighted?'highlighted':''">
1915
  <div class="sf-avatar" x-text="acct.name.charAt(0)"></div>
1916
  <div>
1917
  <div class="sf-company" x-text="acct.name"></div>
1918
  <div class="sf-meta">
1919
  <span class="health-dot"
1920
  :class="acct.health==='green'?'health-green':acct.health==='yellow'?'health-yellow':'health-red'"></span>
1921
+ <span x-text="acct.stage||'Qualified'"></span>
1922
  <span style="margin:0 6px;color:var(--border);">Β·</span>
1923
+ <span x-text="acct.owner?'Owner: '+acct.owner:'Unassigned'"></span>
1924
  <template x-if="acct.churn_risk">
1925
+ <span class="badge"
1926
+ style="margin-left:6px;background:#FEE2E2;color:var(--red);font-size:10px;">Churn Risk</span>
1927
  </template>
1928
  </div>
1929
  </div>
1930
  <div style="text-align:right;">
1931
+ <div class="sf-arr" x-text="acct.arr?'$'+(acct.arr/1000).toFixed(0)+'K':'β€”'"></div>
1932
  <div style="font-size:10px;color:var(--text-3);">ARR</div>
1933
  </div>
1934
  </div>
1935
  </template>
1936
+ <div x-show="sfAccounts.length===0"
1937
  style="font-size:12px;color:var(--text-3);padding:24px;text-align:center;">No accounts loaded β€” reset to
1938
  start an episode.</div>
1939
  </div>
1940
  </div>
1941
 
1942
+ <!-- ════ WORKDAY ════ -->
1943
+ <div x-show="activeAppTab==='workday'">
1944
  <div class="wd-header">
1945
  <span class="wd-logo">Workday</span>
1946
  <span class="wd-tagline">People &amp; HR Operations</span>
1947
+ <template x-for="[app,ver] in Object.entries(schemaVersions)" :key="app">
1948
+ <span x-show="app==='workday'" class="schema-pill" x-text="'schema '+ver"></span>
1949
  </template>
1950
  </div>
1951
  <div class="wd-body">
 
1952
  <div class="wd-stat-row">
1953
  <div class="wd-stat">
1954
  <div class="wd-stat-val" x-text="wdEmployees.length"></div>
 
1959
  <div class="wd-stat-label">Pending Tasks</div>
1960
  </div>
1961
  <div class="wd-stat">
1962
+ <div class="wd-stat-val" x-text="slaLogged?'1':'0'"></div>
1963
  <div class="wd-stat-label">SLA Events</div>
1964
  </div>
1965
  </div>
 
 
1966
  <div class="wd-section-title">Employee Records</div>
1967
  <div class="wd-tasks-header">
1968
  <span>Employee</span><span>Department</span><span>Level</span><span>Status</span>
1969
  </div>
1970
  <template x-for="emp in wdEmployees" :key="emp.id">
1971
+ <div class="wd-task-row" :class="emp.highlighted?'highlighted':''">
1972
  <div>
1973
  <div class="wd-emp" x-text="emp.name"></div>
1974
  <div style="font-size:10px;font-family:var(--font-mono);color:var(--text-3);" x-text="emp.id"></div>
1975
  </div>
1976
+ <span class="wd-dept" x-text="emp.department||'β€”'"></span>
1977
+ <span class="wd-level" x-text="emp.level||'β€”'"></span>
1978
  <div>
1979
  <span class="badge"
1980
  :style="emp.status==='active'?'background:#D1FAE5;color:#065F46;':emp.status==='pending'?'background:#FEF3C7;color:#92400E;':'background:var(--bg);color:var(--text-2);'"
1981
+ x-text="emp.status||'active'"></span>
1982
  </div>
1983
  </div>
1984
  </template>
1985
+ <div x-show="wdEmployees.length===0"
1986
  style="font-size:12px;color:var(--text-3);padding:24px;text-align:center;">No employee records β€” reset
1987
  to start an episode.</div>
1988
  </div>
 
1991
  </div><!-- /app-inner -->
1992
  </div><!-- /app-content -->
1993
 
1994
+ <!-- ── Agent Log ── -->
1995
  <div class="agent-log">
1996
  <div class="log-header">
1997
  <span class="log-title">Agent Log</span>
 
2000
  <div class="live-badge-dot live-dot"></div>
2001
  Live
2002
  </div>
2003
+ <button @click="actionLog=[]"
2004
+ style="font-size:11px;color:var(--text-3);cursor:pointer;background:none;border:none;padding:0;">Clear</button>
 
 
2005
  </div>
2006
  </div>
2007
  <div class="log-scroll" id="log-scroll">
2008
+ <div x-show="actionLog.length===0" style="font-size:11px;color:var(--text-3);padding:12px 0;">Waiting for
2009
+ episode to start…</div>
2010
+ <template x-for="(entry,i) in actionLog" :key="i">
 
2011
  <div class="log-row">
2012
+ <span class="log-step" x-text="'#'+entry.step"></span>
2013
  <div class="log-indicator"
2014
  :class="entry.type==='success'?'ind-success':entry.type==='error'?'ind-error':entry.type==='reset'?'ind-reset':'ind-info'">
2015
  </div>
2016
  <div class="log-body">
2017
  <div class="log-tags">
2018
  <template x-if="entry.app">
2019
+ <span class="log-app-tag" :class="'tag-'+entry.app" x-text="entry.app"></span>
2020
  </template>
2021
  <template x-if="entry.operation">
2022
+ <span class="log-op" x-text="entry.operation+'()'"></span>
2023
  </template>
2024
+ <template x-if="entry.reward!==undefined">
2025
+ <span class="log-reward" :style="entry.reward>=0?'color:var(--green)':'color:var(--red)'"
2026
+ x-text="(entry.reward>=0?'+':'')+entry.reward.toFixed(4)"></span>
2027
  </template>
2028
  </div>
2029
  <div class="log-msg" x-text="entry.message"></div>
 
2035
 
2036
  </main>
2037
 
2038
+ <!-- ── RIGHT SIDEBAR ── -->
2039
  <aside class="sidebar-right">
2040
 
 
2041
  <div class="score-block">
2042
  <div class="section-label" style="text-align:left;">Episode Score</div>
2043
  <div class="score-gauge-wrap">
2044
  <canvas id="gaugeChart" width="110" height="110"></canvas>
2045
  <div class="score-center">
2046
+ <div class="score-val" x-text="(currentScore*100).toFixed(0)"></div>
2047
  <div class="score-sub">/ 100</div>
2048
  </div>
2049
  </div>
2050
  <div style="font-size:11px;color:var(--text-2);">
2051
+ <span x-text="stepCount+' steps'"></span>
2052
  <span style="margin:0 6px;color:var(--border);">Β·</span>
2053
+ <span x-text="maxSteps+' max'"></span>
2054
  </div>
2055
  </div>
2056
 
 
2057
  <div class="breakdown-block">
2058
  <div class="section-label">Score Breakdown</div>
2059
  <template x-for="comp in rewardComponents" :key="comp.key">
2060
  <div class="breakdown-row">
2061
  <div class="breakdown-label-row">
2062
  <span class="breakdown-label" x-text="comp.label"></span>
2063
+ <span class="breakdown-pct" x-text="(comp.value*100).toFixed(0)+'%'"></span>
2064
  </div>
2065
  <div class="bar-track">
2066
+ <div class="bar-fill" :style="'width:'+(comp.value*100)+'%;background:'+comp.color"></div>
2067
  </div>
2068
  </div>
2069
  </template>
2070
  </div>
2071
 
 
2072
  <div style="padding:14px 16px;border-bottom:1px solid var(--border);flex-shrink:0;">
2073
  <div class="section-label">Reward Per Step</div>
2074
  <canvas id="rewardChart" style="width:100%;max-height:90px;"></canvas>
2075
  </div>
2076
 
 
2077
  <div class="stats-block">
2078
  <div class="section-label">Episode Stats</div>
2079
  <div class="stat-row">
2080
  <span class="stat-label">Violations</span>
2081
+ <span class="stat-val" :style="violationCount>0?'color:var(--red)':'color:var(--green)'"
2082
  x-text="violationCount"></span>
2083
  </div>
2084
  <div class="stat-row">
 
2087
  </div>
2088
  <div class="stat-row">
2089
  <span class="stat-label">Schema errors</span>
2090
+ <span class="stat-val" :style="schemaErrorCount>0?'color:var(--red)':''" x-text="schemaErrorCount"></span>
 
2091
  </div>
2092
  <div class="stat-row">
2093
  <span class="stat-label">Workflow</span>
2094
+ <span class="stat-val" x-text="workflowId||'β€”'"></span>
2095
  </div>
2096
  </div>
2097
 
 
2098
  <div class="violations-block">
2099
  <div class="section-label">Rule Violations</div>
2100
+ <div x-show="violations.length===0" style="font-size:11px;color:var(--text-3);">None this episode.</div>
2101
+ <template x-for="(v,i) in violations.slice(-10)" :key="i">
2102
  <div class="violation-item" x-text="v"></div>
2103
  </template>
2104
  </div>
 
2136
  { id: 'workday', label: 'Workday', color: '#FF6B35' },
2137
  ],
2138
  activeAppTab: 'zendesk',
 
 
2139
 
2140
+ // β˜… cinematicAction drives the action banner and tab switching
2141
+ // Set synchronously on every step event β€” no async queue needed
2142
+ cinematicAction: null,
2143
+
2144
+ appOpenCounts: { zendesk: 0, jira: 0, salesforce: 0, workday: 0 },
2145
  jiraCards: [],
2146
  zdTickets: [],
2147
  sfAccounts: [],
 
2167
  violations: [],
2168
  actionLog: [],
2169
 
2170
+ // ─────────────────────────────────────────
2171
  async init() {
2172
  await this.checkHealth();
2173
  _chartInst = this._initRewardChart();
 
2191
  body: JSON.stringify({ workflow_id: this.selectedWorkflow }),
2192
  });
2193
  const data = await r.json();
2194
+ this._clearEpisodeState();
 
 
 
 
 
 
2195
  this._applyObservation(data.observation, null, 0);
2196
  this._updateChart();
2197
  this._updateGauge();
 
2204
  }
2205
  },
2206
 
2207
+ _clearEpisodeState() {
2208
+ this.actionLog = [];
2209
+ this.rewardHistory = [];
2210
+ this.violationCount = 0;
2211
+ this.schemaAdaptCount = 0;
2212
+ this.schemaErrorCount = 0;
2213
+ this.violations = [];
2214
+ this.slaLogged = false;
2215
+ this.cinematicAction = null;
2216
+ },
2217
+
2218
+ // ─────────────────────────────────────────
2219
+ // startAgent β€” plain EventSource, no queue, no async handler
2220
+ // The cinematic tab switch and banner update happen synchronously
2221
+ // inside _handleSSEEvent, which is always called from onmessage.
2222
+ // Pacing between steps comes from asyncio.sleep(0.3) on the server.
2223
  startAgent() {
2224
  if (this.isRunning) return;
2225
  this.isRunning = true;
2226
+ this.cinematicAction = null;
2227
  const url = `${this.envUrl}/ui/run-agent?workflow_id=${this.selectedWorkflow}`;
2228
  _sseInst = new EventSource(url);
2229
+
2230
  _sseInst.onmessage = (e) => {
2231
  try {
2232
  const evt = JSON.parse(e.data);
2233
  this._handleSSEEvent(evt);
2234
  } catch { }
2235
  };
2236
+
2237
  _sseInst.onerror = () => {
2238
+ // Ignore error noise after we've already closed the stream ourselves
2239
+ if (!_sseInst || !this.isRunning) return;
2240
  this.isRunning = false;
2241
+ this.cinematicAction = null;
2242
+ _sseInst.close(); _sseInst = null;
2243
  this._pushLog({ type: 'error', step: this.stepCount, message: 'SSE connection error.' });
2244
  };
2245
  },
2246
 
2247
+ // Delay close so any in-flight 'done' event still arrives
2248
  stopAgent() {
2249
  this.isRunning = false;
2250
+ this.cinematicAction = null;
2251
+ const inst = _sseInst; _sseInst = null;
2252
+ if (inst) setTimeout(() => inst.close(), 1500);
2253
  },
2254
 
2255
+ // ─────────────────────────────────────────
2256
+ // _handleSSEEvent β€” SYNCHRONOUS (no await, no sleep)
2257
+ // This is the key fix: removing the async queue that caused the deadlock.
2258
  _handleSSEEvent(evt) {
2259
  if (evt.type === 'reset') {
2260
+ this._clearEpisodeState();
 
 
 
 
 
 
2261
  this._applyObservation(evt.observation, null, 0);
2262
  this._pushLog({ type: 'reset', step: 0, message: `Episode started β€” Workflow ${evt.workflow_id}` });
2263
+
2264
  } else if (evt.type === 'step') {
2265
  const obs = evt.observation;
2266
+
2267
+ // β˜… CINEMATIC: switch tab immediately + show action banner
2268
+ // This is the "movie" effect β€” happens the instant the event arrives
2269
+ if (evt.action) {
2270
+ this.activeAppTab = evt.action.app;
2271
+ this.cinematicAction = {
2272
+ app: evt.action.app,
2273
+ operation: evt.action.operation,
2274
+ args: evt.action.args || {},
2275
+ step: evt.step,
2276
+ summary: this.actionSummary(evt.action),
2277
+ };
2278
+ }
2279
+
2280
  this._applyObservation(obs, evt.action, evt.reward);
2281
+
2282
+ // Track schema events
2283
+ if (obs.message && (obs.message.includes('Stale schema') || obs.message.includes('Schema error') || obs.message.includes('schema_error'))) {
2284
+ this.schemaErrorCount++;
2285
+ }
2286
+ if (obs.message && (obs.message.includes('adapted') || obs.message.includes('schema_adapted'))) {
2287
+ this.schemaAdaptCount++;
2288
+ }
2289
+ if (obs.message && obs.message.includes('SLA event logged')) {
2290
+ this.slaLogged = true;
2291
+ }
2292
+
2293
  this.rewardHistory.push(evt.reward);
2294
  this._updateChart();
2295
  this._updateGauge();
2296
+
2297
  if (obs.rule_violations && obs.rule_violations.length > 0) {
2298
  this.violations.push(...obs.rule_violations);
2299
  this.violationCount += obs.rule_violations.length;
2300
  }
2301
+
2302
+ // β˜… Update the card in the active app UI
2303
  this._highlightAppCard(evt.action, obs.message);
2304
+
2305
  this._pushLog({
2306
  type: evt.reward < 0 ? 'error' : (evt.reward > 0.05 ? 'success' : 'info'),
2307
  step: evt.step,
 
2310
  reward: evt.reward,
2311
  message: obs.message,
2312
  });
2313
+
2314
+ // Max steps hit
2315
+ if (evt.done) {
2316
+ this.isRunning = false;
2317
+ this.cinematicAction = null;
2318
+ this._pushLog({
2319
+ type: 'info', step: evt.step,
2320
+ message: `Max steps reached. Score: ${(obs.current_score || 0).toFixed(3)} | ${obs.pending_steps?.length ? obs.pending_steps.length + ' step(s) remaining' : 'Workflow complete'}`,
2321
+ });
2322
+ }
2323
+
2324
  } else if (evt.type === 'done') {
2325
+ const score = evt.final_score || this.currentScore;
2326
+ this.cinematicAction = null;
2327
+ this.currentScore = score;
2328
+ this._updateGauge();
2329
+ this.isRunning = false;
2330
  this._pushLog({
2331
+ type: evt.completed ? 'success' : 'info',
2332
+ step: evt.steps,
2333
+ message: `Episode ended. Final score: ${score.toFixed(3)} | Workflow ${evt.completed ? 'completed' : 'incomplete'} | ${evt.steps} steps`,
2334
  });
2335
+ const inst = _sseInst; _sseInst = null;
2336
+ if (inst) inst.close();
2337
+
2338
  } else if (evt.type === 'error') {
2339
+ this.cinematicAction = null;
2340
+ this.isRunning = false;
2341
+ this._pushLog({ type: 'error', step: evt.step || this.stepCount, message: evt.message || 'Unknown error' });
2342
+ const inst = _sseInst; _sseInst = null;
2343
+ if (inst) inst.close();
2344
  }
2345
  },
2346
 
2347
+ // ─────────────────────────────────────────
2348
+ // Helper labels for the action banner
2349
+ appLabel(app) { return this.appTabs.find(t => t.id === app)?.label || 'App'; },
2350
+ appInitial(app) { return this.appLabel(app).slice(0, 2).toUpperCase(); },
2351
+ appColor(app) { return this.appTabs.find(t => t.id === app)?.color || '#111827'; },
2352
+
2353
+ operationLabel(action) {
2354
+ if (!action) return 'Waiting for agent action';
2355
+ return `${this.appLabel(action.app)}: ${String(action.operation || '').replace(/_/g, ' ')}()`;
2356
+ },
2357
+
2358
+ actionSummary(action) {
2359
+ const args = action?.args || {};
2360
+ const id = args.ticket_number || args.issue_id || args.account_id ||
2361
+ args.employee_id || args.ticket_id || args.customer_id;
2362
+ const target = id ? `Target: ${id}` : 'Inspecting records';
2363
+ const argStr = JSON.stringify(args).slice(0, 80);
2364
+ return `${target} Β· ${argStr}`;
2365
+ },
2366
+
2367
+ // ─────────────────────────────────────────
2368
+ // β˜… _highlightAppCard β€” updates the card that the agent just touched
2369
+ // Also handles create / update mutations so the UI reflects state changes
2370
  _highlightAppCard(action, message) {
2371
  if (!action) return;
2372
  const app = action.app;
2373
  const args = action.args || {};
2374
 
2375
+ // Clear all highlights first
2376
  this.jiraCards = this.jiraCards.map(c => ({ ...c, highlighted: false }));
2377
  this.zdTickets = this.zdTickets.map(t => ({ ...t, highlighted: false }));
2378
  this.sfAccounts = this.sfAccounts.map(a => ({ ...a, highlighted: false }));
2379
  this.wdEmployees = this.wdEmployees.map(e => ({ ...e, highlighted: false }));
2380
 
2381
  if (app === 'jira') {
 
2382
  const id = args.issue_id || 'JIRA-001';
2383
  this.jiraCards = this.jiraCards.map(c => ({ ...c, highlighted: c.id === id }));
2384
+
2385
+ if (action.operation === 'create_issue' && message) {
2386
  const m = message.match(/Created (JIRA-\d+)/);
2387
+ if (m && !this.jiraCards.find(c => c.id === m[1])) {
2388
+ this.jiraCards = [{
2389
+ id: m[1], title: args.title || 'New Issue',
2390
+ priority: args.priority || args.severity || args.urgency_level || 'p1',
2391
+ assignee: args.assignee || args.owner || args.assigned_to || null,
2392
+ status: 'open',
2393
+ linked_zendesk: args.linked_zendesk || args.zendesk_ticket || null,
2394
+ highlighted: true,
2395
+ }, ...this.jiraCards];
 
 
 
2396
  }
2397
  }
2398
  if (action.operation === 'assign_owner' || action.operation === 'update_status') {
2399
+ this.jiraCards = this.jiraCards.map(c => c.id !== id ? c : {
2400
+ ...c,
2401
+ assignee: args.assignee || args.owner || args.assigned_to || c.assignee,
2402
+ status: args.status || args.state || args.current_state || c.status,
2403
+ highlighted: true,
 
 
 
 
 
2404
  });
2405
  }
2406
  if (action.operation === 'link_zendesk_ticket') {
2407
+ this.jiraCards = this.jiraCards.map(c => c.id !== id ? c : { ...c, linked_zendesk: args.zendesk_ticket_number, highlighted: true });
2408
  }
2409
  }
2410
 
2411
  if (app === 'zendesk') {
 
2412
  const id = args.ticket_number || 'ZD-001';
2413
  this.zdTickets = this.zdTickets.map(t => ({ ...t, highlighted: t.id === id }));
2414
  if (action.operation === 'acknowledge_ticket') {
2415
+ this.zdTickets = this.zdTickets.map(t => t.id !== id ? t : { ...t, state: 'open', highlighted: true });
2416
  }
2417
  if (action.operation === 'assign_agent') {
2418
+ const agent = args.agent_email || args.handler || args.assigned_agent;
2419
+ this.zdTickets = this.zdTickets.map(t => t.id !== id ? t : { ...t, agent, highlighted: true });
2420
  }
2421
  if (action.operation === 'resolve_ticket') {
2422
+ this.zdTickets = this.zdTickets.map(t => t.id !== id ? t : { ...t, state: 'resolved', highlighted: true });
2423
  }
2424
  }
2425
 
2426
  if (app === 'salesforce') {
 
2427
  const id = args.account_id || 'ACME-001';
2428
  this.sfAccounts = this.sfAccounts.map(a => ({ ...a, highlighted: a.id === id }));
2429
  if (action.operation === 'flag_churn_risk') {
2430
+ this.sfAccounts = this.sfAccounts.map(a => a.id !== id ? a : { ...a, health: 'red', churn_risk: true, highlighted: true });
2431
  }
2432
  if (action.operation === 'assign_account_owner') {
2433
  const owner = args.owner || args.owner_name || args.account_owner || args.rep_email;
2434
+ this.sfAccounts = this.sfAccounts.map(a => a.id !== id ? a : { ...a, owner, highlighted: true });
2435
  }
2436
  }
2437
 
2438
  if (app === 'workday') {
 
2439
  const id = args.employee_id || 'EMP-001';
2440
  this.wdEmployees = this.wdEmployees.map(e => ({ ...e, highlighted: e.id === id }));
2441
+ if (action.operation === 'create_onboarding_task' && !this.wdEmployees.find(e => e.id === id)) {
2442
+ this.wdEmployees = [{
2443
+ id, name: args.name || id,
2444
+ department: args.department || 'support',
2445
+ level: args.level || args.job_level || args.seniority || 'IC1',
2446
+ status: 'pending', highlighted: true,
2447
+ }, ...this.wdEmployees];
 
 
2448
  }
2449
  if (action.operation === 'log_sla_event') this.slaLogged = true;
2450
  }
2451
  },
2452
 
2453
+ // ─────────────────────────────────────────
2454
  _applyObservation(obs, action, reward) {
2455
  this.workflowId = obs.workflow_id || '';
2456
  this.workflowGoal = obs.workflow_goal || '';
2457
  this.schemaHints = obs.schema_hints || {};
2458
  this.activeRules = obs.active_rules || {};
2459
  this.stepCount = obs.step_count || 0;
2460
+ this.completedSteps = (obs.completed_steps || []).slice();
2461
  this.policyDriftActive = obs.policy_drift_active || false;
2462
 
2463
  const newScore = obs.current_score || 0.001;
 
2496
  const rb = obs.reward_breakdown || {};
2497
  this.rewardComponents.forEach(c => { c.value = rb[c.key] ?? 0; });
2498
 
 
2499
  if (obs.app_states) this._parseAppStates(obs.app_states);
2500
  },
2501
 
2502
+ // ─────────────────────────────────────────
2503
  _parseAppStates(states) {
 
 
 
2504
  const parseDictStr = (str) => {
2505
  if (!str) return [];
2506
  return str.split('\n').filter(Boolean).map(line => {
2507
  try {
2508
+ return JSON.parse(
2509
+ line.replace(/'/g, '"')
2510
+ .replace(/\bTrue\b/g, 'true')
2511
+ .replace(/\bFalse\b/g, 'false')
2512
+ .replace(/\bNone\b/g, 'null')
2513
+ );
 
2514
  } catch { return null; }
2515
  }).filter(Boolean);
2516
  };
2517
 
 
2518
  const jiraRaw = parseDictStr(states.jira || '');
2519
  if (jiraRaw.length > 0 || this.jiraCards.length === 0) {
2520
+ const m = new Map(this.jiraCards.map(c => [c.id, c]));
2521
  jiraRaw.forEach(r => {
2522
+ const id = r.issue_id || r.id; if (!id) return;
2523
+ const ex = m.get(id) || {};
2524
+ m.set(id, {
2525
+ ...ex, id,
2526
+ title: r.title || ex.title || 'Untitled',
 
 
2527
  priority: r.priority || r.severity || r.urgency_level || ex.priority || 'p2',
2528
  assignee: r.assignee || r.owner || r.assigned_to || ex.assignee || null,
2529
  status: r.status || r.state || r.current_state || ex.status || 'open',
 
2531
  highlighted: ex.highlighted || false,
2532
  });
2533
  });
2534
+ this.jiraCards = Array.from(m.values());
2535
  }
2536
 
 
2537
  const zdRaw = parseDictStr(states.zendesk || '');
2538
  if (zdRaw.length > 0 || this.zdTickets.length === 0) {
2539
+ const m = new Map(this.zdTickets.map(t => [t.id, t]));
2540
  zdRaw.forEach(r => {
2541
+ const id = r.ticket_number || r.id; if (!id) return;
2542
+ const ex = m.get(id) || {};
2543
+ m.set(id, {
 
2544
  ...ex, id,
2545
  subject: r.title || ex.subject || 'Support ticket',
2546
  urgency: r.urgency || r.priority || r.impact_level || ex.urgency || 'p2',
 
2550
  highlighted: ex.highlighted || false,
2551
  });
2552
  });
2553
+ this.zdTickets = Array.from(m.values());
2554
  }
2555
 
 
2556
  const sfRaw = parseDictStr(states.salesforce || '');
2557
  if (sfRaw.length > 0 || this.sfAccounts.length === 0) {
2558
+ const m = new Map(this.sfAccounts.map(a => [a.id, a]));
2559
  sfRaw.forEach(r => {
2560
+ const id = r.account_id || r.id; if (!id) return;
2561
+ const ex = m.get(id) || {};
2562
+ m.set(id, {
 
2563
  ...ex, id,
2564
  name: r.company_name || ex.name || id,
2565
  health: r.health || r.account_health || r.risk_score || ex.health || 'green',
 
2570
  highlighted: ex.highlighted || false,
2571
  });
2572
  });
2573
+ this.sfAccounts = Array.from(m.values());
2574
  }
2575
 
 
2576
  const wdRaw = parseDictStr(states.workday || '');
2577
  if (wdRaw.length > 0 || this.wdEmployees.length === 0) {
2578
+ const m = new Map(this.wdEmployees.map(e => [e.id, e]));
2579
  wdRaw.forEach(r => {
2580
+ const id = r.employee_id || r.id; if (!id) return;
2581
+ const ex = m.get(id) || {};
2582
+ m.set(id, {
 
2583
  ...ex, id,
2584
  name: r.name || ex.name || id,
2585
  department: r.department || ex.department || 'β€”',
 
2588
  highlighted: ex.highlighted || false,
2589
  });
2590
  });
2591
+ this.wdEmployees = Array.from(m.values());
2592
  }
2593
  },
2594
 
2595
+ // ─────────────────────────────────────────
2596
  _pushLog(entry) {
2597
  this.actionLog.push(entry);
2598
  this.$nextTick(() => {