pinokiod 5.1.10 → 5.1.17

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 (56) hide show
  1. package/kernel/api/fs/download_worker.js +158 -0
  2. package/kernel/api/fs/index.js +95 -91
  3. package/kernel/api/index.js +3 -0
  4. package/kernel/bin/index.js +5 -2
  5. package/kernel/environment.js +19 -2
  6. package/kernel/git.js +972 -1
  7. package/kernel/index.js +65 -30
  8. package/kernel/peer.js +1 -2
  9. package/kernel/plugin.js +0 -8
  10. package/kernel/procs.js +92 -36
  11. package/kernel/prototype.js +45 -22
  12. package/kernel/shells.js +30 -6
  13. package/kernel/sysinfo.js +33 -13
  14. package/kernel/util.js +61 -24
  15. package/kernel/workspace_status.js +131 -7
  16. package/package.json +1 -1
  17. package/pipe/index.js +1 -1
  18. package/server/index.js +1169 -350
  19. package/server/public/create-launcher.js +157 -2
  20. package/server/public/install.js +135 -41
  21. package/server/public/style.css +32 -1
  22. package/server/public/tab-link-popover.js +45 -14
  23. package/server/public/terminal-settings.js +51 -35
  24. package/server/public/urldropdown.css +89 -3
  25. package/server/socket.js +12 -7
  26. package/server/views/agents.ejs +4 -3
  27. package/server/views/app.ejs +798 -30
  28. package/server/views/bootstrap.ejs +2 -1
  29. package/server/views/checkpoints.ejs +1014 -0
  30. package/server/views/checkpoints_registry_beta.ejs +260 -0
  31. package/server/views/columns.ejs +4 -4
  32. package/server/views/connect.ejs +1 -0
  33. package/server/views/d.ejs +74 -4
  34. package/server/views/download.ejs +28 -28
  35. package/server/views/editor.ejs +4 -5
  36. package/server/views/env_editor.ejs +1 -1
  37. package/server/views/file_explorer.ejs +1 -1
  38. package/server/views/index.ejs +3 -1
  39. package/server/views/init/index.ejs +2 -1
  40. package/server/views/install.ejs +2 -1
  41. package/server/views/net.ejs +9 -7
  42. package/server/views/network.ejs +15 -14
  43. package/server/views/pro.ejs +5 -2
  44. package/server/views/prototype/index.ejs +2 -1
  45. package/server/views/registry_link.ejs +76 -0
  46. package/server/views/rows.ejs +4 -4
  47. package/server/views/screenshots.ejs +1 -0
  48. package/server/views/settings.ejs +1 -0
  49. package/server/views/shell.ejs +4 -6
  50. package/server/views/terminal.ejs +528 -38
  51. package/server/views/tools.ejs +1 -0
  52. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/1764297248545 +0 -45
  53. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/1764335557118 +0 -45
  54. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/1764335834126 +0 -45
  55. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/events +0 -12
  56. package/undefined/logs/dev/plugin/cursor-agent.git/pinokio.js/latest +0 -45
@@ -185,6 +185,213 @@ body.frozen {
185
185
  flex-direction: column !important;
186
186
  }
187
187
  }
188
+ .ai-consent-overlay {
189
+ position: fixed;
190
+ inset: 0;
191
+ display: none;
192
+ place-items: center;
193
+ z-index: 2147483000;
194
+ padding: 16px;
195
+ background: rgba(15, 17, 23, 0.52);
196
+ backdrop-filter: blur(10px);
197
+ pointer-events: auto;
198
+ }
199
+ .ai-consent-overlay.is-visible {
200
+ display: grid;
201
+ }
202
+ .ai-consent-modal {
203
+ width: min(520px, 100%);
204
+ background: #fdfdfd;
205
+ color: #0f1117;
206
+ border-radius: 16px;
207
+ border: 1px solid #e8eaf0;
208
+ box-shadow: 0 24px 60px rgba(0, 0, 0, 0.18);
209
+ padding: 22px 22px 18px;
210
+ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
211
+ animation: ai-pop 160ms ease-out;
212
+ pointer-events: auto;
213
+ }
214
+ .ai-consent-overlay * {
215
+ pointer-events: auto;
216
+ }
217
+ body.dark .ai-consent-modal {
218
+ background: #0f1117;
219
+ color: #eef1f8;
220
+ border-color: rgba(255, 255, 255, 0.1);
221
+ box-shadow: 0 28px 70px rgba(0, 0, 0, 0.5);
222
+ }
223
+ .ai-consent-icon {
224
+ font-size: 28px;
225
+ margin-bottom: 10px;
226
+ }
227
+ .ai-consent-header {
228
+ display: flex;
229
+ align-items: flex-start;
230
+ gap: 12px;
231
+ margin-bottom: 6px;
232
+ }
233
+ .ai-consent-header .ai-consent-text {
234
+ flex: 1;
235
+ }
236
+ .ai-consent-title {
237
+ margin: 0 0 10px;
238
+ font-size: 18px;
239
+ font-weight: 700;
240
+ }
241
+ .ai-consent-list {
242
+ margin: 0 0 14px;
243
+ padding-left: 18px;
244
+ color: #2c3143;
245
+ line-height: 1.5;
246
+ }
247
+ .ai-consent-desc {
248
+ margin: 6px 0 12px;
249
+ font-size: 14px;
250
+ color: #2f3241;
251
+ }
252
+ body.dark .ai-consent-desc {
253
+ color: #cdd5e7;
254
+ }
255
+ .ai-consent-list li {
256
+ margin-bottom: 6px;
257
+ font-size: 14px;
258
+ }
259
+ body.dark .ai-consent-list {
260
+ color: #cdd5e7;
261
+ }
262
+ .ai-consent-remember {
263
+ display: flex;
264
+ align-items: center;
265
+ gap: 8px;
266
+ font-size: 14px;
267
+ color: #232635;
268
+ margin-bottom: 14px;
269
+ }
270
+ body.dark .ai-consent-remember {
271
+ color: #d8def0;
272
+ }
273
+ .ai-consent-actions {
274
+ display: flex;
275
+ justify-content: flex-end;
276
+ gap: 10px;
277
+ }
278
+ .ai-btn-primary,
279
+ .ai-btn-secondary {
280
+ font: 600 14px -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
281
+ padding: 9px 14px;
282
+ border-radius: 10px;
283
+ border: 1px solid transparent;
284
+ cursor: pointer;
285
+ transition: transform 120ms ease, box-shadow 120ms ease, background 120ms ease;
286
+ }
287
+ .ai-btn-primary {
288
+ background: linear-gradient(135deg, #2b7bff, #5e5df0);
289
+ color: #fff;
290
+ box-shadow: 0 10px 24px rgba(46, 111, 255, 0.25);
291
+ }
292
+ .ai-btn-primary:hover {
293
+ transform: translateY(-1px);
294
+ box-shadow: 0 12px 28px rgba(46, 111, 255, 0.3);
295
+ }
296
+ .ai-btn-secondary {
297
+ background: #f5f7fb;
298
+ color: #0f1117;
299
+ border-color: #d8dbe5;
300
+ }
301
+ .ai-btn-secondary:hover {
302
+ transform: translateY(-1px);
303
+ box-shadow: 0 10px 22px rgba(0, 0, 0, 0.06);
304
+ }
305
+ body.dark .ai-btn-secondary {
306
+ background: rgba(255, 255, 255, 0.08);
307
+ color: #eef1f8;
308
+ border-color: rgba(255, 255, 255, 0.12);
309
+ }
310
+ .ai-consent-manage {
311
+ margin-top: 10px;
312
+ background: none;
313
+ border: none;
314
+ color: #3c6df0;
315
+ font: 600 13px -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
316
+ cursor: pointer;
317
+ padding: 0;
318
+ }
319
+ body.dark .ai-consent-manage {
320
+ color: #9bb4ff;
321
+ }
322
+ .ai-manage-sheet {
323
+ position: fixed;
324
+ inset: auto 12px 12px auto;
325
+ width: min(360px, 92vw);
326
+ background: #ffffff;
327
+ color: #0f1117;
328
+ border-radius: 14px;
329
+ box-shadow: 0 18px 48px rgba(0, 0, 0, 0.18);
330
+ border: 1px solid #e6e8ee;
331
+ padding: 14px 16px 12px;
332
+ z-index: 2147483001;
333
+ font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", sans-serif;
334
+ }
335
+ body.dark .ai-manage-sheet {
336
+ background: #0f1117;
337
+ color: #eef1f8;
338
+ border-color: rgba(255, 255, 255, 0.08);
339
+ box-shadow: 0 20px 50px rgba(0, 0, 0, 0.45);
340
+ }
341
+ .ai-manage-header {
342
+ display: flex;
343
+ justify-content: space-between;
344
+ align-items: center;
345
+ margin-bottom: 10px;
346
+ }
347
+ .ai-manage-header h3 {
348
+ margin: 0;
349
+ font-size: 15px;
350
+ font-weight: 700;
351
+ }
352
+ #ai-manage-close {
353
+ border: none;
354
+ background: none;
355
+ font-size: 20px;
356
+ cursor: pointer;
357
+ color: inherit;
358
+ }
359
+ .ai-manage-list {
360
+ max-height: 240px;
361
+ overflow: auto;
362
+ }
363
+ .ai-manage-item {
364
+ display: flex;
365
+ justify-content: space-between;
366
+ align-items: center;
367
+ padding: 10px 0;
368
+ border-bottom: 1px solid #f0f2f7;
369
+ font-size: 14px;
370
+ }
371
+ body.dark .ai-manage-item {
372
+ border-color: rgba(255, 255, 255, 0.08);
373
+ }
374
+ .ai-manage-item button {
375
+ background: none;
376
+ border: none;
377
+ color: #e14b4b;
378
+ cursor: pointer;
379
+ font-weight: 600;
380
+ }
381
+ .ai-manage-empty {
382
+ padding: 8px 0 4px;
383
+ font-size: 13px;
384
+ opacity: 0.7;
385
+ }
386
+ .ai-manage-footer {
387
+ margin-top: 8px;
388
+ display: flex;
389
+ justify-content: flex-end;
390
+ }
391
+ @keyframes ai-pop {
392
+ from { transform: translateY(8px) scale(0.98); opacity: 0; }
393
+ to { transform: none; opacity: 1; }
394
+ }
188
395
  </style>
189
396
  <link href="/terminal.css" rel="stylesheet"/>
190
397
  <script>
@@ -278,6 +485,294 @@ const sanitizePreviewLine = (value) => {
278
485
  : fallbackPreviewSanitizer
279
486
  return sanitizer(value || "")
280
487
  }
488
+ const formatConsentPath = (value) => {
489
+ if (!value) return "this app folder"
490
+ const normalized = value.replace(/\\/g, "/")
491
+ const marker = "/pinokio/api/"
492
+ const lower = normalized.toLowerCase()
493
+ const idx = lower.lastIndexOf(marker)
494
+ if (idx >= 0) {
495
+ const suffix = normalized.slice(idx + marker.length).replace(/^\/+/, "")
496
+ return `~/pinokio/api/${suffix}`
497
+ }
498
+ if (normalized.startsWith("~")) return normalized
499
+ return normalized
500
+ }
501
+ const getSafeLocalStorage = () => {
502
+ try {
503
+ const store = window.localStorage
504
+ const key = "__aiConsentTest__"
505
+ store.setItem(key, "1")
506
+ store.removeItem(key)
507
+ return store
508
+ } catch (_) {
509
+ return null
510
+ }
511
+ }
512
+ const createRunControls = () => {
513
+ const container = document.querySelector(".run")
514
+ const play = container ? container.querySelector(".play") : null
515
+ const starting = container ? container.querySelector(".starting") : null
516
+ const stop = container ? container.querySelector(".stop") : null
517
+ const set = (state) => {
518
+ if (!play || !starting || !stop) return
519
+ play.classList.toggle("hidden", state !== "idle")
520
+ starting.classList.toggle("hidden", state !== "starting")
521
+ stop.classList.toggle("hidden", state !== "running")
522
+ }
523
+ return { set }
524
+ }
525
+ const createAiConsentManager = (context = {}) => {
526
+ const storage = getSafeLocalStorage()
527
+ const storageSupported = !!storage
528
+ const PREFIX = "pinokio:ai-consent:"
529
+ const extractCwdFromUri = (uri) => {
530
+ if (!uri || typeof uri !== "string") return ""
531
+ try {
532
+ const url = new URL(uri, window.location.origin)
533
+ const cwd = url.searchParams.get("cwd")
534
+ return cwd || ""
535
+ } catch (_) {
536
+ return ""
537
+ }
538
+ }
539
+ const normalizeSource = (value) => {
540
+ if (!value || typeof value !== "string") return ""
541
+ return value.trim().replace(/\\/g, "/").replace(/\/+$/, "")
542
+ }
543
+ const source = normalizeSource(context.folder || extractCwdFromUri(context.uri || ""))
544
+ const label = formatConsentPath(source)
545
+ const storageKey = source ? `${PREFIX}${encodeURIComponent(source)}` : null
546
+ const state = { overlay: null, manage: null }
547
+ const guessProviderLabel = () => {
548
+ const raw = (context.uri || "") + " " + (context.folder || "")
549
+ const lower = raw.toLowerCase()
550
+ const pattern = /\/plugin\/([^/]+)\/([^/]+)\/pinokio\.js/
551
+ const match = lower.match(pattern)
552
+ const candidate = match && match[2] ? match[2] : (match && match[1] ? match[1] : "")
553
+ if (!candidate) return ""
554
+ const clean = candidate.replace(/[-_]+/g, " ").trim()
555
+ if (!clean) return ""
556
+ return clean.split(/\s+/).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ")
557
+ }
558
+ const providerLabel = guessProviderLabel()
559
+
560
+ const listConsents = () => {
561
+ if (!storageSupported) return []
562
+ const items = []
563
+ for (let i = 0; i < storage.length; i++) {
564
+ const key = storage.key(i)
565
+ if (key && key.startsWith(PREFIX)) {
566
+ const decoded = decodeURIComponent(key.slice(PREFIX.length))
567
+ items.push({
568
+ key,
569
+ path: decoded,
570
+ label: formatConsentPath(decoded),
571
+ })
572
+ }
573
+ }
574
+ return items.sort((a, b) => a.label.localeCompare(b.label))
575
+ }
576
+ const remember = () => {
577
+ if (!storageSupported || !storageKey) return
578
+ storage.setItem(storageKey, source || "1")
579
+ }
580
+ const revoke = (key) => {
581
+ if (!storageSupported || !key) return
582
+ storage.removeItem(key)
583
+ }
584
+ const isRemembered = () => {
585
+ if (!storageSupported || !storageKey) return false
586
+ return storage.getItem(storageKey) !== null
587
+ }
588
+ const renderManageList = () => {
589
+ if (!state.manage) return
590
+ const listEl = state.manage.querySelector(".ai-manage-list")
591
+ if (!listEl) return
592
+ if (!storageSupported) {
593
+ listEl.innerHTML = `<div class="ai-manage-empty">Browser storage is unavailable, so permissions cannot be saved.</div>`
594
+ return
595
+ }
596
+ const entries = listConsents()
597
+ if (entries.length === 0) {
598
+ listEl.innerHTML = `<div class="ai-manage-empty">No saved permissions.</div>`
599
+ return
600
+ }
601
+ listEl.innerHTML = entries.map((entry) => {
602
+ return `
603
+ <div class="ai-manage-item">
604
+ <div class="ai-manage-path">${entry.label}</div>
605
+ <button type="button" data-ai-revoke="${entry.key}">Revoke</button>
606
+ </div>
607
+ `
608
+ }).join("")
609
+ listEl.querySelectorAll("[data-ai-revoke]").forEach((btn) => {
610
+ btn.addEventListener("click", () => {
611
+ const targetKey = btn.getAttribute("data-ai-revoke")
612
+ revoke(targetKey)
613
+ renderManageList()
614
+ })
615
+ })
616
+ }
617
+ const ensureManageSheet = () => {
618
+ if (state.manage) return state.manage
619
+ const sheet = document.createElement("div")
620
+ sheet.className = "ai-manage-sheet"
621
+ sheet.hidden = true
622
+ sheet.innerHTML = `
623
+ <div class="ai-manage-header">
624
+ <h3>AI permissions</h3>
625
+ <button type="button" id="ai-manage-close">×</button>
626
+ </div>
627
+ <div class="ai-manage-list"></div>
628
+ <div class="ai-manage-footer">
629
+ <button class="ai-btn-secondary" id="ai-manage-clear">Clear all</button>
630
+ </div>
631
+ `
632
+ document.body.appendChild(sheet)
633
+ sheet.querySelector("#ai-manage-close")?.addEventListener("click", () => {
634
+ sheet.hidden = true
635
+ })
636
+ sheet.querySelector("#ai-manage-clear")?.addEventListener("click", () => {
637
+ if (storageSupported) {
638
+ listConsents().forEach((entry) => revoke(entry.key))
639
+ }
640
+ renderManageList()
641
+ })
642
+ state.manage = sheet
643
+ return sheet
644
+ }
645
+ const openManage = () => {
646
+ const sheet = ensureManageSheet()
647
+ renderManageList()
648
+ sheet.hidden = false
649
+ }
650
+ const ensureOverlay = () => {
651
+ if (state.overlay) return state.overlay
652
+ const overlay = document.createElement("div")
653
+ overlay.className = "ai-consent-overlay"
654
+ overlay.style.display = "none"
655
+ overlay.style.pointerEvents = "auto"
656
+ overlay.innerHTML = `
657
+ <div class="ai-consent-modal" role="dialog" aria-modal="true" aria-labelledby="ai-consent-title">
658
+ <div class="ai-consent-header">
659
+ <div class="ai-consent-icon" aria-hidden="true"><i class="fa-solid fa-shield-halved"></i></div>
660
+ <div class="ai-consent-text">
661
+ <h2 class="ai-consent-title" id="ai-consent-title"></h2>
662
+ <p class="ai-consent-desc"></p>
663
+ </div>
664
+ </div>
665
+ <ul class="ai-consent-list">
666
+ <li><strong>Will access:</strong> <span class="ai-consent-scope"></span></li>
667
+ <li><strong>Won't access:</strong> other folders</li>
668
+ </ul>
669
+ <label class="ai-consent-remember">
670
+ <input type="checkbox" id="ai-consent-remember">
671
+ <span class="ai-consent-remember-label"></span>
672
+ </label>
673
+ <div class="ai-consent-actions">
674
+ <button class="ai-btn-secondary" type="button" data-ai-consent-cancel>Cancel</button>
675
+ <button class="ai-btn-primary" type="button" data-ai-consent-next>Next</button>
676
+ </div>
677
+ <button class="ai-consent-manage" type="button" data-ai-consent-manage>Manage permissions</button>
678
+ </div>
679
+ `
680
+ document.body.appendChild(overlay)
681
+ state.overlay = overlay
682
+ return overlay
683
+ }
684
+ const prompt = () => new Promise((resolve) => {
685
+ if (!source) {
686
+ resolve({ allow: true, remember: false })
687
+ return
688
+ }
689
+ const overlay = ensureOverlay()
690
+ const title = overlay.querySelector(".ai-consent-title")
691
+ const desc = overlay.querySelector(".ai-consent-desc")
692
+ const scope = overlay.querySelector(".ai-consent-scope")
693
+ const rememberLabel = overlay.querySelector(".ai-consent-remember-label")
694
+ const rememberBox = overlay.querySelector("#ai-consent-remember")
695
+ const nextBtn = overlay.querySelector("[data-ai-consent-next]")
696
+ const cancelBtn = overlay.querySelector("[data-ai-consent-cancel]")
697
+ const manageBtn = overlay.querySelector("[data-ai-consent-manage]")
698
+
699
+ if (title) title.textContent = "Remote Network Access"
700
+ if (desc) {
701
+ const target = providerLabel || "a remote API"
702
+ desc.textContent = `This script may send files inside ${label} to ${target}.`
703
+ }
704
+ if (scope) scope.textContent = label
705
+ if (rememberLabel) rememberLabel.textContent = `Always allow for ${label}`
706
+ if (rememberBox) {
707
+ rememberBox.checked = storageSupported
708
+ rememberBox.disabled = !storageSupported
709
+ }
710
+
711
+ const close = () => {
712
+ overlay.classList.remove("is-visible")
713
+ setTimeout(() => {
714
+ overlay.style.display = "none"
715
+ }, 150)
716
+ }
717
+ const cleanup = () => {
718
+ nextBtn?.removeEventListener("click", onNext)
719
+ cancelBtn?.removeEventListener("click", onCancel)
720
+ manageBtn?.removeEventListener("click", onManage)
721
+ overlay.removeEventListener("click", onBackdrop)
722
+ }
723
+ const onNext = () => {
724
+ cleanup()
725
+ close()
726
+ const rememberChoice = rememberBox ? rememberBox.checked : false
727
+ resolve({ allow: true, remember: storageSupported && rememberChoice })
728
+ }
729
+ const onCancel = () => {
730
+ cleanup()
731
+ close()
732
+ resolve({ allow: false, remember: false })
733
+ }
734
+ const onManage = (event) => {
735
+ event.preventDefault()
736
+ openManage()
737
+ }
738
+ const onBackdrop = (event) => {
739
+ if (event.target === overlay) {
740
+ onCancel()
741
+ }
742
+ }
743
+
744
+ overlay.style.display = "grid"
745
+ requestAnimationFrame(() => overlay.classList.add("is-visible"))
746
+
747
+ nextBtn?.addEventListener("click", onNext)
748
+ cancelBtn?.addEventListener("click", onCancel)
749
+ manageBtn?.addEventListener("click", onManage)
750
+ overlay.addEventListener("click", onBackdrop)
751
+ if (nextBtn && typeof nextBtn.focus === "function") {
752
+ nextBtn.focus()
753
+ }
754
+ })
755
+ const ensureAllowed = async () => {
756
+ if (!source) return true
757
+ if (isRemembered()) {
758
+ return true
759
+ }
760
+ const result = await prompt()
761
+ if (!result || !result.allow) {
762
+ return false
763
+ }
764
+ if (result.remember) {
765
+ remember()
766
+ }
767
+ return true
768
+ }
769
+
770
+ return {
771
+ ensureAllowed,
772
+ showChipIfRemembered: () => {},
773
+ openManage,
774
+ }
775
+ }
281
776
  document.addEventListener("DOMContentLoaded", async () => {
282
777
  <% if (error) { %>
283
778
  document.querySelector(".requirements .content").innerHTML = '<div class="loading"><i class="fa-solid fa-circle-exclamation"></i> <%=error%></div>'
@@ -305,6 +800,12 @@ document.addEventListener("DOMContentLoaded", async () => {
305
800
  const scriptUri = <% if (script_path) { %><%- JSON.stringify(script_path) %><% } else { %>null<% } %>
306
801
  const scriptCwd = <% if (cwd) { %><%- JSON.stringify(cwd) %><% } else { %>null<% } %>
307
802
  const scriptAction = <% if (typeof action !== 'undefined') { %><%- JSON.stringify(action) %><% } else { %>null<% } %>
803
+ const consentManager = createAiConsentManager({
804
+ folder: scriptCwd,
805
+ uri: scriptUri || ("~" + location.pathname)
806
+ })
807
+ const runControls = createRunControls()
808
+ consentManager.showChipIfRemembered()
308
809
  class RPC {
309
810
  constructor() {
310
811
  this.socket = new Socket()
@@ -392,9 +893,7 @@ document.addEventListener("DOMContentLoaded", async () => {
392
893
  text: `[Success] All steps complete`,
393
894
  })
394
895
  */
395
- document.querySelector(".run .play").classList.remove("hidden")
396
- document.querySelector(".run .stop").classList.add("hidden")
397
- document.querySelector(".run .starting").classList.add("hidden")
896
+ runControls.set("idle")
398
897
  }
399
898
  stop() {
400
899
  const params = {
@@ -436,10 +935,9 @@ document.addEventListener("DOMContentLoaded", async () => {
436
935
  })
437
936
  })
438
937
  }
439
- start(mode) {
440
- return new Promise(async (resolve, reject) => {
938
+ async start(mode) {
441
939
  // await this.save()
442
- await this.socket.close()
940
+ this.socket.close()
443
941
 
444
942
  const searchParams = new URLSearchParams(location.search)
445
943
  const query = Object.fromEntries(searchParams)
@@ -474,9 +972,7 @@ document.addEventListener("DOMContentLoaded", async () => {
474
972
  if (packet.type === 'start') {
475
973
  refreshParent(packet)
476
974
  reloadMemory()
477
- document.querySelector(".run .play").classList.add("hidden")
478
- document.querySelector(".run .starting").classList.add("hidden")
479
- document.querySelector(".run .stop").classList.remove("hidden")
975
+ runControls.set("running")
480
976
  if (packet.data && packet.data.description) {
481
977
  if ('current' in packet.data) {
482
978
  document.querySelector("#status-window").innerHTML = `<b>
@@ -527,17 +1023,14 @@ document.addEventListener("DOMContentLoaded", async () => {
527
1023
  document.querySelector("#progress-window").classList.add("hidden")
528
1024
  document.querySelector("#progress-bar").style.width = "0%"
529
1025
  }
530
- document.querySelector(".run .play").classList.add("hidden")
531
- document.querySelector(".run .starting").classList.add("hidden")
532
- document.querySelector(".run .stop").classList.remove("hidden")
1026
+ runControls.set("running")
533
1027
  } else if (packet.type === 'disconnect') {
534
1028
  refreshParent(packet)
535
1029
  reloadMemory()
536
1030
  this.term.write("\r\nDisconnected...\r\n")
537
1031
  document.querySelector("#status-window").innerHTML = "<b>Ready</b>"
538
1032
  this.socket.close()
539
- document.querySelector(".run .play").classList.remove("hidden")
540
- document.querySelector(".run .stop").classList.add("hidden")
1033
+ runControls.set("idle")
541
1034
  this.resizeSync.reset()
542
1035
 
543
1036
  <% if (kill_message) { %>
@@ -629,9 +1122,7 @@ document.addEventListener("DOMContentLoaded", async () => {
629
1122
  })
630
1123
  }
631
1124
  */
632
- document.querySelector(".run .play").classList.add("hidden")
633
- document.querySelector(".run .starting").classList.add("hidden")
634
- document.querySelector(".run .stop").classList.remove("hidden")
1125
+ runControls.set("running")
635
1126
  } else if (packet.type === 'resize') {
636
1127
  this.resizeSync.handleResizePacket(packet)
637
1128
  // } else if (packet.type === "key.set") {
@@ -869,9 +1360,7 @@ document.addEventListener("DOMContentLoaded", async () => {
869
1360
  text: `${packet.data}`,
870
1361
  })
871
1362
  } else if (packet.type === "error") {
872
- document.querySelector(".run .play").classList.remove("hidden")
873
- document.querySelector(".run .starting").classList.add("hidden")
874
- document.querySelector(".run .stop").classList.add("hidden")
1363
+ runControls.set("idle")
875
1364
 
876
1365
 
877
1366
  document.querySelector("#error-screen").classList.remove("hidden")
@@ -942,9 +1431,7 @@ document.addEventListener("DOMContentLoaded", async () => {
942
1431
  type: 'success'
943
1432
  })
944
1433
  */
945
- document.querySelector(".run .play").classList.remove("hidden")
946
- document.querySelector(".run .starting").classList.add("hidden")
947
- document.querySelector(".run .stop").classList.add("hidden")
1434
+ runControls.set("idle")
948
1435
  }, 0)
949
1436
  //this.socket.close()
950
1437
  }
@@ -967,10 +1454,20 @@ document.addEventListener("DOMContentLoaded", async () => {
967
1454
  // let pageBottom = document.querySelector("#end")
968
1455
  // pageBottom.scrollIntoView()
969
1456
  })
970
- })
971
1457
  }
972
1458
  async run (mode) {
973
1459
  this.mode = (mode ? mode : "run")
1460
+ const allowed = await consentManager.ensureAllowed()
1461
+ if (!allowed) {
1462
+ runControls.set("idle")
1463
+ n.Noty({
1464
+ text: "Run canceled; AI agents remain blocked for this folder.",
1465
+ type: "warning",
1466
+ timeout: 4000
1467
+ })
1468
+ return false
1469
+ }
1470
+ runControls.set("starting")
974
1471
 
975
1472
  // if (dirty) {
976
1473
  // await this.save()
@@ -985,6 +1482,7 @@ document.addEventListener("DOMContentLoaded", async () => {
985
1482
  await this.createTerm(xtermTheme.Tomorrow)
986
1483
  <% } %>
987
1484
  await this.start(mode)
1485
+ return true
988
1486
  }
989
1487
  async uploadFiles(files, overlay) {
990
1488
  const localFiles = Array.isArray(files) ? files : []
@@ -1294,8 +1792,8 @@ document.addEventListener("DOMContentLoaded", async () => {
1294
1792
  <% } %>
1295
1793
  let config = {
1296
1794
  scrollback: 9999999,
1297
- fontSize: 12,
1298
- //fontSize: 14,
1795
+ fontSize: 14,
1796
+ fontFamily: 'monospace',
1299
1797
  theme,
1300
1798
  //theme: xtermTheme.FrontEndDelight
1301
1799
  //theme: xtermTheme.Afterglow
@@ -1769,15 +2267,11 @@ document.addEventListener("DOMContentLoaded", async () => {
1769
2267
  // }, (stream) => {
1770
2268
  // console.log("#", stream)
1771
2269
  // })
1772
- document.querySelector(".run .play").classList.remove("hidden")
1773
- document.querySelector(".run .starting").classList.add("hidden")
1774
- document.querySelector(".run .stop").classList.add("hidden")
2270
+ runControls.set("idle")
1775
2271
  })
1776
2272
  }
1777
2273
  if (document.querySelector(".play")) {
1778
2274
  document.querySelector(".play").addEventListener("click", async (e) => {
1779
- document.querySelector(".run .play").classList.add("hidden")
1780
- document.querySelector(".run .starting").classList.remove("hidden")
1781
2275
  await rpc.run()
1782
2276
  })
1783
2277
  }
@@ -1785,17 +2279,13 @@ document.addEventListener("DOMContentLoaded", async () => {
1785
2279
 
1786
2280
  <% if (stop) { %>
1787
2281
  await rpc.stop()
1788
- document.querySelector(".run .play").classList.remove("hidden")
1789
- document.querySelector(".run .starting").classList.add("hidden")
1790
- document.querySelector(".run .stop").classList.add("hidden")
2282
+ runControls.set("idle")
1791
2283
  <% } else { %>
1792
2284
  <% if (run) { %>
1793
2285
  // run (query params run=true)
1794
- document.querySelector(".run .play").classList.add("hidden")
1795
- document.querySelector(".run .starting").classList.remove("hidden")
1796
- rpc.run()
2286
+ await rpc.run()
1797
2287
  <% } else { %>
1798
- rpc.run("listen")
2288
+ await rpc.run("listen")
1799
2289
  <% } %>
1800
2290
  <% } %>
1801
2291
  <% } %>
@@ -1280,6 +1280,7 @@ document.addEventListener('DOMContentLoaded', function() {
1280
1280
  <a href="/connect" class='tab'><i class="fa-solid fa-plug"></i><div class='caption'>Login</div></a>
1281
1281
  <a class='tab' href="<%=portal%>" target="_blank"><i class="fa-solid fa-question"></i><div class='caption'>Help</div></a>
1282
1282
  <a class='tab' id='genlog' href="/logs"><i class="fa-solid fa-laptop-code"></i><div class='caption'>Logs</div></a>
1283
+ <a class='tab' href="/checkpoints"><i class="fa-solid fa-clock-rotate-left"></i><div class='caption'>Checkpoints</div></a>
1283
1284
  <a class='tab' href="/screenshots"><i class="fa-solid fa-camera"></i><div class='caption'>Screenshots</div></a>
1284
1285
  <a class='tab selected' href="/tools"><i class="fa-solid fa-toolbox"></i><div class='caption'>Installed Tools</div></a>
1285
1286
  <a class='tab' href="/agents"><i class="fa-solid fa-robot"></i><div class='caption'>Agents</div></a>