@wipcomputer/wip-ldm-os 0.4.85-alpha.30 → 0.4.85-alpha.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/bin/ldm.js +187 -1
  2. package/lib/deploy.mjs +36 -1
  3. package/package.json +10 -2
  4. package/scripts/test-boot-dir-truth.mjs +158 -0
  5. package/scripts/test-boot-hook-registration.mjs +136 -0
  6. package/scripts/test-boot-payload-trim.mjs +200 -0
  7. package/scripts/test-crc-pair-login-flow.mjs +76 -1
  8. package/scripts/test-deploy-hook-ownership.mjs +160 -0
  9. package/scripts/test-doctor-hook-dedupe.mjs +188 -0
  10. package/scripts/test-kaleidoscope-onboarding-copy.mjs +170 -0
  11. package/scripts/test-kaleidoscope-public-stats-baseline.mjs +129 -0
  12. package/scripts/test-kaleidoscope-qr-authenticator-confirmation.mjs +89 -0
  13. package/shared/boot/boot-config.json +18 -8
  14. package/shared/docs/dev-guide-wipcomputerinc.md.tmpl +5 -4
  15. package/shared/rules/security.md +4 -0
  16. package/src/boot/README.md +24 -1
  17. package/src/boot/boot-config.json +18 -8
  18. package/src/boot/boot-hook.mjs +118 -28
  19. package/src/boot/installer.mjs +33 -10
  20. package/src/hosted-mcp/.env.example +4 -0
  21. package/src/hosted-mcp/app/footer.js +2 -2
  22. package/src/hosted-mcp/app/kaleidoscope-login.html +486 -42
  23. package/src/hosted-mcp/app/wip-logo.png +0 -0
  24. package/src/hosted-mcp/demo/footer.js +2 -2
  25. package/src/hosted-mcp/demo/index.html +166 -44
  26. package/src/hosted-mcp/demo/login.html +87 -23
  27. package/src/hosted-mcp/demo/privacy.html +4 -214
  28. package/src/hosted-mcp/demo/tos.html +4 -189
  29. package/src/hosted-mcp/legal/internet-services/kaleidoscope/index.html +257 -0
  30. package/src/hosted-mcp/legal/internet-services/terms/site.html +224 -168
  31. package/src/hosted-mcp/legal/legal-footer.js +75 -0
  32. package/src/hosted-mcp/legal/legal.css +166 -0
  33. package/src/hosted-mcp/legal/privacy/en-ww/index.html +4 -221
  34. package/src/hosted-mcp/legal/privacy/index.html +253 -0
  35. package/src/hosted-mcp/server.mjs +662 -35
Binary file
@@ -22,7 +22,7 @@
22
22
  + '<p style="margin:2px 0 0;">Learning Dreaming Machines</p>'
23
23
  + '<p style="margin:8px 0 0;">Copyright &copy; 2026 WIP Computer, Inc. All rights reserved.</p>'
24
24
  + '<p style="margin:4px 0 0;">'
25
- + '<a href="/legal/privacy/en-ww/" style="color:#a8a4a0;text-decoration:none;">Privacy Policy</a> &nbsp;|&nbsp; '
25
+ + '<a href="/legal/privacy/" style="color:#a8a4a0;text-decoration:none;">Privacy Policy</a> &nbsp;|&nbsp; '
26
26
  + '<a href="/legal/internet-services/terms/site.html" style="color:#a8a4a0;text-decoration:none;">Terms of Use</a></p>'
27
27
  + '<p style="margin:4px 0 0;">'
28
28
  + '<a href="/agent.txt" style="color:#a8a4a0;text-decoration:none;">Are you an AI Agent?</a></p>'
@@ -31,7 +31,7 @@
31
31
  inner.innerHTML = '<p style="margin:0;">WIP Computer, Inc.</p>'
32
32
  + '<p style="margin:2px 0 0;">Learning Dreaming Machines</p>'
33
33
  + '<p style="margin:8px 0 0;">Copyright &copy; 2026 WIP Computer, Inc. All rights reserved. &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;'
34
- + '<a href="/legal/privacy/en-ww/" style="color:#a8a4a0;text-decoration:none;">Privacy Policy</a> &nbsp;|&nbsp; '
34
+ + '<a href="/legal/privacy/" style="color:#a8a4a0;text-decoration:none;">Privacy Policy</a> &nbsp;|&nbsp; '
35
35
  + '<a href="/legal/internet-services/terms/site.html" style="color:#a8a4a0;text-decoration:none;">Terms of Use</a></p>'
36
36
  + '<p style="margin:4px 0 0;">'
37
37
  + '<a href="/agent.txt" style="color:#a8a4a0;text-decoration:none;">Are you an AI Agent?</a> &nbsp;|&nbsp; '
@@ -18,7 +18,7 @@
18
18
  "@type": "Offer",
19
19
  "price": "0",
20
20
  "priceCurrency": "USD",
21
- "description": "Demo with $5.00 simulated wallet"
21
+ "description": "Demo with $10.00 simulated wallet"
22
22
  },
23
23
  "author": {
24
24
  "@type": "Organization",
@@ -70,6 +70,10 @@ html, body {
70
70
  -webkit-font-smoothing: antialiased;
71
71
  }
72
72
 
73
+ body.chat-active {
74
+ background: #FFFFFF;
75
+ }
76
+
73
77
  @media (min-width: 768px) {
74
78
  html, body { overflow: hidden; }
75
79
  }
@@ -200,6 +204,7 @@ html, body {
200
204
  bottom: 0;
201
205
  max-width: 600px;
202
206
  margin: 0 auto;
207
+ background: #FFFFFF;
203
208
  }
204
209
 
205
210
  .chat-header {
@@ -210,7 +215,7 @@ html, body {
210
215
  text-align: center;
211
216
  border-bottom: 1px solid rgba(0, 0, 0, 0.06);
212
217
  flex-shrink: 0;
213
- background: rgba(255, 253, 245, 0.8);
218
+ background: rgba(255, 255, 255, 0.8);
214
219
  -webkit-backdrop-filter: saturate(180%) blur(20px);
215
220
  backdrop-filter: saturate(180%) blur(20px);
216
221
  }
@@ -287,6 +292,22 @@ html, body {
287
292
  margin-top: 6px;
288
293
  }
289
294
 
295
+ .bubble a,
296
+ .bubble .chat-link-button {
297
+ color: var(--accent);
298
+ font: inherit;
299
+ text-decoration: underline;
300
+ text-underline-offset: 2px;
301
+ }
302
+
303
+ .bubble .chat-link-button {
304
+ display: inline;
305
+ padding: 0;
306
+ border: 0;
307
+ background: transparent;
308
+ cursor: pointer;
309
+ }
310
+
290
311
  /* Camera preview circle */
291
312
  .camera-container {
292
313
  display: flex;
@@ -477,7 +498,7 @@ html, body {
477
498
  padding-bottom: calc(12px + env(safe-area-inset-bottom, 0px));
478
499
  border-top: 1px solid var(--input-border);
479
500
  flex-shrink: 0;
480
- background: var(--bg);
501
+ background: #FFFFFF;
481
502
  }
482
503
 
483
504
  .chat-input-row {
@@ -574,7 +595,7 @@ html, body {
574
595
  <!-- ── CHAT PAGE ── -->
575
596
  <div class="chat-page" id="chatPage">
576
597
  <div class="chat-header" style="position:relative;">
577
- <a id="kscopeIcon" onclick="sessionStorage.clear();location.reload();" style="cursor:pointer;position:absolute;left:16px;top:50%;transform:translateY(-50%);display:flex;align-items:center;"></a>
598
+ <button id="kscopeIcon" type="button" onclick="logoutAndSignBackIn()" aria-label="Log out and sign back in" style="cursor:pointer;position:absolute;left:16px;top:50%;transform:translateY(-50%);display:flex;align-items:center;border:0;background:transparent;padding:0;"></button>
578
599
  <div class="chat-header-name">Kaleidoscope</div>
579
600
  <div class="chat-header-sub"></div>
580
601
  </div>
@@ -593,6 +614,20 @@ html, body {
593
614
  <canvas id="photoCanvas" style="display:none;"></canvas>
594
615
 
595
616
  <script>
617
+ // Direct /demo entry is not the public start path. The login handoff writes
618
+ // lesa-token before replacing into /demo, so authenticated continuations pass.
619
+ (function gateDirectDemoEntry() {
620
+ var hasDemoToken = false;
621
+ try {
622
+ hasDemoToken = !!sessionStorage.getItem('lesa-token');
623
+ } catch (err) {
624
+ hasDemoToken = false;
625
+ }
626
+ if (!hasDemoToken) {
627
+ location.replace('/login?next=/demo');
628
+ }
629
+ })();
630
+
596
631
  // ── WebAuthn Helpers ──
597
632
 
598
633
  function b64urlToBytes(b64url) {
@@ -687,6 +722,7 @@ async function doCreateAccount() {
687
722
  if (result.success) {
688
723
  sessionStorage.setItem('lesa-token', result.apiKey);
689
724
  sessionStorage.setItem('lesa-agent', result.agentId);
725
+ if (result.tenantId) sessionStorage.setItem('lesa-tenant', result.tenantId);
690
726
  sessionStorage.setItem('lesa-new-account', 'true');
691
727
  setLoginStatus('Account created.', 'success');
692
728
  setTimeout(function() { showChat(); }, 400);
@@ -757,6 +793,7 @@ async function doSignIn() {
757
793
  if (result.success) {
758
794
  sessionStorage.setItem('lesa-token', result.apiKey);
759
795
  sessionStorage.setItem('lesa-agent', result.agentId);
796
+ if (result.tenantId) sessionStorage.setItem('lesa-tenant', result.tenantId);
760
797
  setLoginStatus('Signed in.', 'success');
761
798
  setTimeout(function() { showChat(); }, 400);
762
799
  } else {
@@ -867,7 +904,7 @@ function showAuthButton() {
867
904
  btn.innerHTML = '<span style="filter:brightness(0) invert(1);">🫆</span> Authorize';
868
905
  btn.onclick = function() {
869
906
  container.remove();
870
- addMessage('Authorized', 'user');
907
+ addMessage('Authorizing', 'user');
871
908
  doFaceIDAuth();
872
909
  };
873
910
  container.appendChild(btn);
@@ -878,17 +915,52 @@ function showAuthButton() {
878
915
 
879
916
  // ── Settings ──
880
917
 
881
- var WALLET_BALANCE = "$5.00";
918
+ var WALLET_BALANCE = "$10.00";
882
919
  var IMAGE_COST = "$0.01";
883
- var IMAGE_BALANCE_AFTER = "$4.99";
920
+ var IMAGE_BALANCE_AFTER = "$9.99";
884
921
  var IMAGE_API_NAME = "Grok Imagine";
885
922
  var IMAGE_API_PROVIDER = "xAI";
923
+ var IS_RETURNING_USER_FLOW = false;
924
+
925
+ async function getWalletSummary() {
926
+ var walletBalance = WALLET_BALANCE;
927
+ var walletCost = IMAGE_COST;
928
+ try {
929
+ var token = sessionStorage.getItem('lesa-token');
930
+ if (!token) return { balance: walletBalance, cost: walletCost };
931
+ var wRes = await fetch('/demo/api/wallet', { headers: { 'Authorization': 'Bearer ' + token } });
932
+ var wData = await wRes.json();
933
+ if (wData.balance) walletBalance = wData.balance;
934
+ if (wData.cost) walletCost = wData.cost;
935
+ } catch {}
936
+ return { balance: walletBalance, cost: walletCost };
937
+ }
938
+
939
+ function currentAccountLabel() {
940
+ return sessionStorage.getItem('lesa-agent') || 'this account';
941
+ }
942
+
943
+ function canonicalAccountId(value) {
944
+ if (typeof value !== 'string') return null;
945
+ var trimmed = value.trim();
946
+ if (!trimmed) return null;
947
+ return trimmed.indexOf('acct:') === 0 ? trimmed.slice(5) : trimmed;
948
+ }
949
+
950
+ function isSameAccountAuthorization(result, activeToken, activeTenant) {
951
+ var sessionAccount = canonicalAccountId(activeTenant);
952
+ var resultAccount = canonicalAccountId(result && result.tenantId);
953
+ if (sessionAccount && resultAccount) {
954
+ return sessionAccount === resultAccount;
955
+ }
956
+ return !!(activeToken && result && result.apiKey === activeToken);
957
+ }
886
958
 
887
959
  // ── Image Prompts ──
888
960
 
889
- var PROMPT_PHOTO = "kaleidoscope pattern made from mirrored and reflected colors and shapes derived from a photograph, shot on expired 35mm film with heavy grain and light leaks, warm analog color bleed, faded and sun-bleached, symmetrical radial reflections but organic and imperfect, no face visible just abstracted colors and textures, washed out like a Boards of Canada album cover, square format, lo-fi analog photography aesthetic, slight overexposure, nostalgic and haunting, no digital sharpness, no CGI, no illustration, no fingers, no text";
961
+ var PROMPT_PHOTO = "Create an abstract kaleidoscope image from the colors, light, and shapes in the user's photo. Use mirrored radial symmetry, analog film grain, warm color bleed, soft exposure, organic imperfections, and no text.";
890
962
 
891
- var PROMPT_NO_PHOTO = "abstract kaleidoscope pattern viewed through a real kaleidoscope, shot on expired 35mm film with heavy grain and light leaks, warm amber and deep red analog color palette, faded and sun-bleached, symmetrical radial reflections but organic and imperfect, washed out like the Geogaddi album cover, square format, lo-fi analog photography aesthetic, slight overexposure, nostalgic and haunting, no digital sharpness, no CGI, no illustration, no faces, no fingers, no text";
963
+ var PROMPT_NO_PHOTO = "Create an abstract kaleidoscope image with mirrored radial symmetry, analog film grain, warm color bleed, soft exposure, organic imperfections, and no text.";
892
964
 
893
965
  // ── Demo Flow ──
894
966
 
@@ -896,16 +968,19 @@ async function startDemo() {
896
968
  await delay(600);
897
969
  var isNew = sessionStorage.getItem('lesa-new-account') === 'true';
898
970
  sessionStorage.removeItem('lesa-new-account');
899
- await lesaSays("Hi, I'm L\u0113sa. Welcome to Kaleidoscope.", 1200);
900
- await delay(1000);
901
971
  if (isNew) {
902
- await lesaSays("You just created an account with a passkey. It lives on this device.", 1200);
972
+ IS_RETURNING_USER_FLOW = false;
973
+ await lesaSays("Hi, I'm L\u0113sa. Welcome to Kaleidoscope.", 1200);
974
+ await delay(1000);
975
+ await lesaSays("You just created an account with a passkey. The passkey lives on your phone.", 1200);
976
+ await lesaSays("Going forward, you can use your phone to log into any Work in Progress Computer service.", 1500);
977
+ await lesaSays("Anytime I need your permission to do something, I'll ask, and you authorize with your fingerprint or face. No passwords. Ever.", 1500);
978
+ await lesaSays("Want to see it in action? I'll try to do something that costs money, and you decide whether to let me.", 1200);
903
979
  } else {
904
- await lesaSays("You just logged in with your passkey.", 1200);
980
+ IS_RETURNING_USER_FLOW = true;
981
+ await lesaSays("Hi, " + currentAccountLabel() + ". Welcome back to Kaleidoscope.", 1200);
982
+ await lesaSays("Do you want to try giving me permission with your device to create something that costs money?", 1200);
905
983
  }
906
- await lesaSays("Going forward, you can use this passkey to log into any Work in Progress Computer service.", 1500);
907
- await lesaSays("And I can use it too. Anytime I need your permission to do something, I'll ask, and you authorize with your fingerprint or face. No passwords. Ever.", 1500);
908
- await lesaSays("Want to see it in action? I'll try to do something that costs money, and you decide whether to let me.", 1200);
909
984
 
910
985
  showChoiceButtons([
911
986
  { label: 'Yes, show me', primary: true, action: demoYesShowMe },
@@ -916,22 +991,16 @@ async function startDemo() {
916
991
  // ── "Yes, show me" path ──
917
992
 
918
993
  async function demoYesShowMe() {
919
- var walletBalance = WALLET_BALANCE;
920
- var walletCost = IMAGE_COST;
921
- try {
922
- var token = sessionStorage.getItem('lesa-token');
923
- var wRes = await fetch('/demo/api/wallet', { headers: { 'Authorization': 'Bearer ' + token } });
924
- var wData = await wRes.json();
925
- if (wData.balance) walletBalance = wData.balance;
926
- if (wData.cost) walletCost = wData.cost;
927
- } catch {}
928
- await lesaSays("I have a wallet with " + walletBalance + ". Do I have your permission to spend " + walletCost + " on image generation using the " + IMAGE_API_PROVIDER + " " + IMAGE_API_NAME + " API?", 1000);
994
+ var wallet = await getWalletSummary();
995
+ await lesaSays("I have a wallet with " + wallet.balance + ". Do I have your permission to spend " + wallet.cost + " on image generation using the " + IMAGE_API_PROVIDER + " " + IMAGE_API_NAME + " API?", 1000);
929
996
 
930
997
  showAuthButton();
931
998
  }
932
999
 
933
1000
  async function doFaceIDAuth() {
934
1001
  showTyping();
1002
+ var activeToken = sessionStorage.getItem('lesa-token');
1003
+ var activeTenant = sessionStorage.getItem('lesa-tenant');
935
1004
 
936
1005
  try {
937
1006
  var optRes = await fetch('/webauthn/auth-options', {
@@ -983,6 +1052,13 @@ async function doFaceIDAuth() {
983
1052
  return;
984
1053
  }
985
1054
 
1055
+ if (!isSameAccountAuthorization(result, activeToken, activeTenant)) {
1056
+ removeTyping();
1057
+ await lesaSays("That passkey belongs to a different account. This onboarding session is using " + currentAccountLabel() + ". Restart if you want to switch accounts.", 600);
1058
+ showAuthButton();
1059
+ return;
1060
+ }
1061
+
986
1062
  removeTyping();
987
1063
  await lesaSays("Thanks for authorizing. Let me show you what I can do.", 600);
988
1064
  await delay(500);
@@ -1081,13 +1157,32 @@ async function demoJustMakeOne() {
1081
1157
  // ── "No thanks" path (skip permissions entirely) ──
1082
1158
 
1083
1159
  async function demoNoThanks() {
1084
- await lesaSays("No problem. Let me make you something beautiful.", 1000);
1085
- await generateKaleidoscope(false);
1160
+ await showNoThanksFlow();
1161
+ }
1162
+
1163
+ async function showNoThanksFlow() {
1164
+ if (IS_RETURNING_USER_FLOW) {
1165
+ await showReturningUserNoThanksFlow();
1166
+ return;
1167
+ }
1168
+ await lesaSays("No problem. Let me show you something beautiful.", 1000);
1169
+ await generateKaleidoscope(false, { showWalletReceipt: false, showOutro: false });
1170
+ await showApprovedOutro();
1171
+ }
1172
+
1173
+ async function showReturningUserNoThanksFlow() {
1174
+ await lesaSays("No problem. You can come back anytime.", 1000);
1175
+ await lesaSays("At Work in Progress Computer, we are building the future of AI and human interaction.", 1500);
1176
+ await lesaSaysHTML('We look forward to providing you a streamlined experience across all the AIs you use. Follow us on X <a href="https://x.com/wipcomputer" target="_blank" rel="noopener">@wipcomputer</a> to keep up to date on when we release new features.', 1800);
1177
+ await lesaSays("Made in California by WIP Computer, Inc. Learning Dreaming Machines.", 1200);
1086
1178
  }
1087
1179
 
1088
1180
  // ── Image generation ──
1089
1181
 
1090
- async function generateKaleidoscope(isPhotoPath) {
1182
+ async function generateKaleidoscope(isPhotoPath, options) {
1183
+ options = options || {};
1184
+ var showWalletReceipt = options.showWalletReceipt !== false;
1185
+ var showDefaultOutro = options.showOutro !== false;
1091
1186
  var prompt = isPhotoPath ? PROMPT_PHOTO : PROMPT_NO_PHOTO;
1092
1187
 
1093
1188
  // If photo path, analyze the photo with vision to extract colors/mood
@@ -1104,7 +1199,7 @@ async function generateKaleidoscope(isPhotoPath) {
1104
1199
  });
1105
1200
  var analyzeData = await analyzeRes.json();
1106
1201
  if (analyzeData.description) {
1107
- prompt = "abstract kaleidoscope pattern, dominant colors: " + analyzeData.description + ", symmetrical radial reflections, mirrored geometric shapes in these exact colors, shot on expired 35mm film with heavy grain and light leaks, warm analog color bleed, faded and sun-bleached like a Boards of Canada album cover, no face, no person, no fingers, no spheres, no beads, no text, just abstract color and light";
1202
+ prompt = "Create an abstract kaleidoscope image from these visual details in the user's photo: " + analyzeData.description + ". Use mirrored radial symmetry, analog film grain, warm color bleed, soft exposure, organic imperfections, and no text.";
1108
1203
  }
1109
1204
  } catch (e) {
1110
1205
  // Vision analysis failed... fall back to original PROMPT_PHOTO
@@ -1146,10 +1241,12 @@ async function generateKaleidoscope(isPhotoPath) {
1146
1241
  genBubble.innerHTML = '<img class="kaleidoscope-img" src="' + data.url + '" alt="Your kaleidoscope">';
1147
1242
  scrollToBottom();
1148
1243
 
1149
- var cost = data.cost || IMAGE_COST;
1150
- var balance = data.balance || IMAGE_BALANCE_AFTER;
1151
- await lesaSays("Cost: " + cost + ". Balance: " + balance + ".", 800);
1152
- await showOutro();
1244
+ if (showWalletReceipt) {
1245
+ var cost = data.cost || IMAGE_COST;
1246
+ var balance = data.balance || IMAGE_BALANCE_AFTER;
1247
+ await lesaSays("Cost: " + cost + ". Balance: " + balance + ".", 800);
1248
+ }
1249
+ if (showDefaultOutro) await showOutro();
1153
1250
 
1154
1251
  } catch (err) {
1155
1252
  loadingDiv.remove();
@@ -1161,14 +1258,36 @@ async function generateKaleidoscope(isPhotoPath) {
1161
1258
  }
1162
1259
 
1163
1260
  async function showOutro() {
1164
- await lesaSays("At Work in Progress Computer we are building the future of AI and human interaction.", 1500);
1165
- showTyping(); await delay(2000); removeTyping();
1166
- await lesaSays("We believe permission is a conversation. Your AI asks. You decide. One glance, one tap.", 2000);
1167
- showTyping(); await delay(2000); removeTyping();
1168
- await lesaSays("To see how passkeys work across devices, open wip.computer/demo in any web browser on any computer. And use this device to authenticate.", 2000);
1169
- showTyping(); await delay(2000); removeTyping();
1170
- await lesaSays("Made in California by WIP Computer, Inc. Learning Dreaming Machines.", 1500);
1171
- await lesaSays("This is the end of the demo. Tap icon or refresh page to start over.", 1200);
1261
+ if (IS_RETURNING_USER_FLOW) {
1262
+ await showReturningUserOutro();
1263
+ return;
1264
+ }
1265
+ await showApprovedOutro();
1266
+ }
1267
+
1268
+ async function showApprovedOutro() {
1269
+ await lesaSays("At Work in Progress Computer, we are building the future of AI and human interaction.", 1500);
1270
+ await lesaSays("We believe permission is a conversation. Your AI asks. You decide. One glance, one tap.", 1800);
1271
+ await lesaSaysHTML('To see how passkeys work across devices, open <a href="https://wip.computer/demo" target="_blank" rel="noopener">https://wip.computer/demo</a> in any web browser on any computer and use your phone to authenticate.', 1800);
1272
+ await lesaSays("You can also click the icon at the top of this chat to log out, and then you can immediately log back in with your phone too.", 1800);
1273
+ await lesaSaysHTML('We look forward to providing you a streamlined experience across all the AIs you use. Follow us on X <a href="https://x.com/wipcomputer" target="_blank" rel="noopener">@wipcomputer</a> to keep up to date on when we release new features.', 1800);
1274
+ await lesaSays("Your passkey will keep working after you leave.", 1200);
1275
+ await lesaSays("Made in California by WIP Computer, Inc. Learning Dreaming Machines.", 1200);
1276
+ }
1277
+
1278
+ async function showReturningUserOutro() {
1279
+ await lesaSays("At Work in Progress Computer, we are building the future of AI and human interaction.", 1500);
1280
+ await lesaSays("We believe permission is a conversation. Your AI asks. You decide. One glance, one tap.", 1800);
1281
+ await lesaSaysHTML('We look forward to providing you a streamlined experience across all the AIs you use. Follow us on X <a href="https://x.com/wipcomputer" target="_blank" rel="noopener">@wipcomputer</a> to keep up to date on when we release new features.', 1800);
1282
+ await lesaSays("Made in California by WIP Computer, Inc. Learning Dreaming Machines.", 1200);
1283
+ }
1284
+
1285
+ function logoutAndSignBackIn() {
1286
+ sessionStorage.removeItem('lesa-token');
1287
+ sessionStorage.removeItem('lesa-agent');
1288
+ sessionStorage.removeItem('lesa-tenant');
1289
+ sessionStorage.removeItem('lesa-new-account');
1290
+ window.location.href = '/login?next=/demo';
1172
1291
  }
1173
1292
 
1174
1293
  async function doGameAuth() {
@@ -1191,7 +1310,7 @@ async function doGameAuth() {
1191
1310
  removeTyping();
1192
1311
  await lesaSays("You are not authorized to play war games with me.", 1200);
1193
1312
  }
1194
- await lesaSays("This is the end of the demo. To try again, refresh this page.", 1500);
1313
+ await lesaSays("Your passkey will continue to work.", 1500);
1195
1314
  }
1196
1315
 
1197
1316
  function showAuthButtonForGame() {
@@ -1203,7 +1322,7 @@ function showAuthButtonForGame() {
1203
1322
  btn.innerHTML = '<span style="filter:brightness(0) invert(1);">🫆</span> Authorize';
1204
1323
  btn.onclick = function() {
1205
1324
  container.remove();
1206
- addMessage('Authorized', 'user');
1325
+ addMessage('Authorizing', 'user');
1207
1326
  doGameAuth();
1208
1327
  };
1209
1328
  container.appendChild(btn);
@@ -1223,6 +1342,9 @@ function showCSSKaleidoscope(bubble) {
1223
1342
 
1224
1343
  function showChat() {
1225
1344
  document.getElementById('loginPage').style.display = 'none';
1345
+ document.body.classList.add('chat-active');
1346
+ var footer = document.getElementById('kscope-footer');
1347
+ if (footer) footer.style.display = 'none';
1226
1348
  var chatPage = document.getElementById('chatPage');
1227
1349
  chatPage.style.display = 'flex';
1228
1350
  startDemo();