pinokiod 7.1.12 → 7.1.14

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.
package/kernel/util.js CHANGED
@@ -626,7 +626,11 @@ const find_python = async (root) => {
626
626
  } else {
627
627
  python_pattern = "**/python"; // Matches python, python3, python3.x
628
628
  }
629
- const pythonBinaries = await glob(python_pattern, { nodir: true, cwd: root });
629
+ const pythonBinaries = await glob(python_pattern, {
630
+ nodir: true,
631
+ cwd: root,
632
+ ignore: ["**/.git/**", "**/node_modules/**"],
633
+ });
630
634
  return pythonBinaries
631
635
  }
632
636
  const find_venv = async (root) => {
@@ -636,7 +640,12 @@ const find_venv = async (root) => {
636
640
  } else {
637
641
  python_pattern = "**/python"; // Matches python, python3, python3.x
638
642
  }
639
- const pythonBinaries = await glob(python_pattern, { nodir: true, cwd: root, absolute: true });
643
+ const pythonBinaries = await glob(python_pattern, {
644
+ nodir: true,
645
+ cwd: root,
646
+ absolute: true,
647
+ ignore: ["**/.git/**", "**/node_modules/**"],
648
+ });
640
649
  const venvs = pythonBinaries.map((p) => {
641
650
  return path.resolve(p, "../..")
642
651
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.1.12",
3
+ "version": "7.1.14",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -969,7 +969,7 @@ class Server {
969
969
  async chrome(req, res, type, options) {
970
970
  console.log("Chrome")
971
971
 
972
- let d = Date.now()
972
+ let name = req.params.name
973
973
  console.time("bin check")
974
974
  let { requirements, install_required, requirements_pending, error } = await this.kernel.bin.check({
975
975
  bin: this.kernel.bin.preset("dev"),
@@ -992,7 +992,6 @@ class Server {
992
992
  })
993
993
  }
994
994
 
995
- let name = req.params.name
996
995
  let config = await this.kernel.api.meta(name)
997
996
 
998
997
  if (options && options.requestPermissions && req.agent === "electron" && this.browser && typeof this.browser.requestPermissions === "function") {
@@ -1090,33 +1089,8 @@ class Server {
1090
1089
  // feed = this.newsfeed(gitRemote)
1091
1090
  // }
1092
1091
 
1093
- // git
1094
-
1095
- let c = this.kernel.path("api", name)
1096
-
1097
- // await this.kernel.plugin.init()
1098
- // let plugin = await this.getPlugin(name)
1099
- // let plugin_menu = null
1100
- // if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
1101
- // let running_dynamic = this.running_dynamic(name, plugin.menu)
1102
- // plugin_menu = plugin.menu.concat(running_dynamic)
1103
- // }
1104
-
1105
-
1106
1092
  let current_urls = await this.current_urls(req.originalUrl.slice(1))
1107
1093
 
1108
- let plugin_menu = null
1109
- let plugin_config = safeStructuredClone(this.kernel.plugin.config)
1110
- let plugin = await this.getPlugin(req, plugin_config, name)
1111
- if (plugin && plugin.menu && Array.isArray(plugin.menu)) {
1112
- plugin = safeStructuredClone(plugin)
1113
- let default_plugin_query
1114
- if (req.query) {
1115
- default_plugin_query = req.query
1116
- }
1117
- plugin_menu = this.running_dynamic(name, plugin.menu, default_plugin_query)
1118
- }
1119
-
1120
1094
  let posix_path = Util.p2u(this.kernel.path("api", name))
1121
1095
  let dev_link
1122
1096
  if (posix_path.startsWith("/")) {
@@ -1187,7 +1161,7 @@ class Server {
1187
1161
  current_urls,
1188
1162
  path: this.kernel.path("api", name),
1189
1163
  log_path: this.kernel.path("api", name, "logs"),
1190
- plugin_menu: plugin_menu,
1164
+ plugin_menu: null,
1191
1165
  portal: this.portal,
1192
1166
  install: this.install,
1193
1167
  error: err,
@@ -4790,8 +4764,12 @@ class Server {
4790
4764
  // }
4791
4765
  }
4792
4766
  }
4793
- async terminals(filepath) {
4794
- let venvs = await Util.find_venv(filepath)
4767
+ async terminals(filepath, options = {}) {
4768
+ const includeVenvs = options.includeVenvs !== false
4769
+ let venvs = []
4770
+ if (includeVenvs) {
4771
+ venvs = await Util.find_venv(filepath)
4772
+ }
4795
4773
  let terminal
4796
4774
  const windowsBashPath = await this.resolveWindowsBashPath()
4797
4775
  const hasWindowsBashOption = this.kernel.platform === "win32" && typeof windowsBashPath === "string" && windowsBashPath.length > 0
@@ -10791,7 +10769,7 @@ class Server {
10791
10769
  }))
10792
10770
  this.app.get("/d/*", ex(async (req, res) => {
10793
10771
  let filepath = Util.u2p(req.params[0])
10794
- let terminal = await this.terminals(filepath)
10772
+ let terminal = await this.terminals(filepath, { includeVenvs: false })
10795
10773
  let plugin = await this.getPluginGlobal(req, this.kernel.plugin.config, terminal, filepath)
10796
10774
  let html = ""
10797
10775
  let plugin_menu
@@ -10934,6 +10912,7 @@ class Server {
10934
10912
  install: this.install,
10935
10913
  agent: req.agent,
10936
10914
  theme: this.theme,
10915
+ terminals_url: "/pinokio/d-terminals/" + req.params[0],
10937
10916
  //dynamic: plugin_menu
10938
10917
  dynamic,
10939
10918
  })
@@ -11076,6 +11055,23 @@ class Server {
11076
11055
  res.send("")
11077
11056
  }
11078
11057
  }))
11058
+ this.app.get("/pinokio/d-terminals/*", ex(async (req, res) => {
11059
+ let filepath = Util.u2p(req.params[0])
11060
+ let terminal = await this.terminals(filepath)
11061
+ const html = await new Promise((resolve, reject) => {
11062
+ ejs.renderFile(path.resolve(__dirname, "views/partials/d_terminal_column.ejs"), {
11063
+ userTerminal: terminal,
11064
+ terminals_url: null,
11065
+ }, (err, html) => {
11066
+ if (err) {
11067
+ reject(err)
11068
+ return
11069
+ }
11070
+ resolve(html)
11071
+ })
11072
+ })
11073
+ res.send(html)
11074
+ }))
11079
11075
  this.app.get("/pinokio/dynamic/:name", ex(async (req, res) => {
11080
11076
  // await this.kernel.plugin.init()
11081
11077
 
@@ -12493,7 +12489,7 @@ class Server {
12493
12489
  this.socket = new Socket(this)
12494
12490
  await new Promise((resolve, reject) => {
12495
12491
  this.listening = this.server.listen(this.port, () => {
12496
- console.log(`Server listening on port ${this.port}`)
12492
+ console.log(`Server listening on http://localhost:${this.port}`)
12497
12493
  this.kernel.server_running = true
12498
12494
  resolve()
12499
12495
  });
@@ -3928,9 +3928,6 @@ document.addEventListener("DOMContentLoaded", () => {
3928
3928
  }
3929
3929
  const fallbackUrl = buildAskAiLaunchUrl(tool.href, workspaceCwd);
3930
3930
  if (fallbackUrl) {
3931
- refreshTerminalSessions(fallbackUrl, workspaceCwd, {
3932
- retryDelays: []
3933
- });
3934
3931
  window.location.href = fallbackUrl;
3935
3932
  }
3936
3933
  }
@@ -1105,7 +1105,9 @@
1105
1105
  ui.templateManager.syncTemplateFields(ui.promptTextarea.value, defaults.templateValues || {});
1106
1106
 
1107
1107
  requestAnimationFrame(() => {
1108
- ui.promptTextarea.focus();
1108
+ if (!ui.promptTextarea.value.trim()) {
1109
+ ui.promptTextarea.focus();
1110
+ }
1109
1111
  });
1110
1112
 
1111
1113
  return ui;
@@ -928,6 +928,21 @@ body.dark .create-launcher-page-card {
928
928
  gap: 12px;
929
929
  }
930
930
 
931
+ .create-launcher-page-card .create-launcher-modal-input:focus,
932
+ .create-launcher-page-card .create-launcher-modal-textarea:focus,
933
+ .create-launcher-page-card .create-launcher-modal-template-input:focus {
934
+ outline: none;
935
+ border-color: rgba(79, 70, 229, 0.7);
936
+ box-shadow: 0 0 0 2px rgba(79, 70, 229, 0.16);
937
+ }
938
+
939
+ body.dark .create-launcher-page-card .create-launcher-modal-input:focus,
940
+ body.dark .create-launcher-page-card .create-launcher-modal-textarea:focus,
941
+ body.dark .create-launcher-page-card .create-launcher-modal-template-input:focus {
942
+ border-color: rgba(129, 140, 248, 0.75);
943
+ box-shadow: 0 0 0 2px rgba(129, 140, 248, 0.2);
944
+ }
945
+
931
946
  .create-launcher-page-card .create-launcher-modal-button.cancel {
932
947
  display: none;
933
948
  }
@@ -5302,6 +5302,7 @@ header.navheader .mode-selector .community-mode-toggle {
5302
5302
  let interacted = false
5303
5303
  let global_selector
5304
5304
  let pendingSelectionRetry = null
5305
+ let initialWorkspaceDiskUsageRequested = false
5305
5306
  const scheduleSelectionRetry = (delay = 100) => {
5306
5307
  if (pendingSelectionRetry !== null) {
5307
5308
  return
@@ -5470,10 +5471,12 @@ header.navheader .mode-selector .community-mode-toggle {
5470
5471
  })
5471
5472
  let frame = document.createElement("iframe")
5472
5473
  frame.name = name
5473
- refreshTerminalSessions(href)
5474
5474
  frame.src = href
5475
5475
  frame.classList.add("selected")
5476
5476
  iframe_onerror(frame)
5477
+ frame.addEventListener("load", () => {
5478
+ requestInitialWorkspaceDiskUsage()
5479
+ }, { once: true })
5477
5480
  frame.setAttribute(
5478
5481
  "allow",
5479
5482
  "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; fullscreen *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
@@ -5484,17 +5487,6 @@ header.navheader .mode-selector .community-mode-toggle {
5484
5487
  document.querySelector("main").appendChild(frame)
5485
5488
  loaded[name] = true
5486
5489
  }
5487
- function refreshTerminalSessions(rawUrl, workspaceCwd = "", options = {}) {
5488
- try {
5489
- const discovery = window.PinokioTerminalsDiscovery
5490
- if (!discovery || typeof discovery.refreshTerminalSessions !== "function") {
5491
- return false
5492
- }
5493
- return discovery.refreshTerminalSessions(rawUrl, workspaceCwd, options)
5494
- } catch (_) {
5495
- return false
5496
- }
5497
- }
5498
5490
  installBrowserviewInjectTargetObserver()
5499
5491
  document.addEventListener("click", (e) => {
5500
5492
  interacted = true
@@ -6579,14 +6571,10 @@ const rerenderMenuSection = (container, html) => {
6579
6571
 
6580
6572
 
6581
6573
  // Instantiate a frame with the selected target's href
6582
- let url = target.href
6583
6574
  const targetInjectDescriptors = readLinkInjectDescriptors(target)
6584
- if (!url) {
6575
+ if (!target.href) {
6585
6576
  return
6586
6577
  }
6587
- // document.querySelector("#open-browser").href = url
6588
- // document.querySelector("#clone-tab").setAttribute("data-href", url)
6589
-
6590
6578
  //document.querySelector("#location").value = url
6591
6579
 
6592
6580
  // document.querySelector("#open-location").setAttribute('href', target.href)
@@ -6634,6 +6622,7 @@ const rerenderMenuSection = (container, html) => {
6634
6622
  eventParam.preventDefault()
6635
6623
  eventParam.stopPropagation()
6636
6624
  }
6625
+ requestInitialWorkspaceDiskUsage()
6637
6626
  browserPopoutSurface.show(target)
6638
6627
  return
6639
6628
  }
@@ -6709,7 +6698,6 @@ const rerenderMenuSection = (container, html) => {
6709
6698
  let mode = target.getAttribute("data-mode")
6710
6699
  if (mode === "refresh") {
6711
6700
  if (targetFrame.src !== target.href) {
6712
- refreshTerminalSessions(target.href)
6713
6701
  targetFrame.src = target.href
6714
6702
  }
6715
6703
  } else {
@@ -6720,7 +6708,6 @@ const rerenderMenuSection = (container, html) => {
6720
6708
  let sub = subUrlOf(target.href, targetFrame.src)
6721
6709
  if (!sub) {
6722
6710
  if (targetFrame.src !== target.href) {
6723
- refreshTerminalSessions(target.href)
6724
6711
  targetFrame.src = target.href
6725
6712
  }
6726
6713
  }
@@ -6736,7 +6723,6 @@ const rerenderMenuSection = (container, html) => {
6736
6723
  } else {
6737
6724
  if (force) {
6738
6725
  if (targetFrame.src !== target.href) {
6739
- refreshTerminalSessions(target.href)
6740
6726
  targetFrame.src = target.href
6741
6727
  }
6742
6728
  }
@@ -6758,15 +6744,17 @@ const rerenderMenuSection = (container, html) => {
6758
6744
  } else {
6759
6745
  let frame = document.createElement("iframe")
6760
6746
  frame.name = target.target
6761
- refreshTerminalSessions(target.href)
6762
6747
  frame.src = target.href
6763
- iframe_onerror(frame)
6764
- frame.setAttribute(
6765
- "allow",
6766
- "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; fullscreen *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
6748
+ iframe_onerror(frame)
6749
+ frame.addEventListener("load", () => {
6750
+ requestInitialWorkspaceDiskUsage()
6751
+ }, { once: true })
6752
+ frame.setAttribute(
6753
+ "allow",
6754
+ "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; fullscreen *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
6767
6755
  )
6768
6756
  frame.setAttribute("allowfullscreen", "")
6769
- writeFrameInjectDescriptors(frame, targetInjectDescriptors)
6757
+ writeFrameInjectDescriptors(frame, targetInjectDescriptors)
6770
6758
  publishBrowserviewInjectTargets([frame], { sync: true })
6771
6759
  document.querySelector("main").appendChild(frame)
6772
6760
  loaded[target.target] = true
@@ -6922,14 +6910,17 @@ const rerenderMenuSection = (container, html) => {
6922
6910
 
6923
6911
  document.querySelector(".temp-menu").appendChild(item)
6924
6912
 
6925
- let frame = document.createElement("iframe")
6926
- frame.name = id
6927
- frame.src = item.href
6928
- iframe_onerror(frame)
6929
- frame.setAttribute(
6930
- "allow",
6931
- "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; fullscreen *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
6932
- )
6913
+ let frame = document.createElement("iframe")
6914
+ frame.name = id
6915
+ frame.src = item.href
6916
+ iframe_onerror(frame)
6917
+ frame.addEventListener("load", () => {
6918
+ requestInitialWorkspaceDiskUsage()
6919
+ }, { once: true })
6920
+ frame.setAttribute(
6921
+ "allow",
6922
+ "clipboard-read *; clipboard-write *; accelerometer *; ambient-light-sensor *; autoplay *; battery *; camera *; display-capture *; fullscreen *; gamepad *; geolocation *; gyroscope *; hid *; identity-credentials-get *; microphone *; midi *; otp-credentials *; serial *;"
6923
+ )
6933
6924
  frame.setAttribute("allowfullscreen", "")
6934
6925
  document.querySelector("main").appendChild(frame)
6935
6926
 
@@ -7998,10 +7989,16 @@ const rerenderMenuSection = (container, html) => {
7998
7989
  })
7999
7990
  }
8000
7991
  }
7992
+ const requestInitialWorkspaceDiskUsage = () => {
7993
+ if (initialWorkspaceDiskUsageRequested) {
7994
+ return
7995
+ }
7996
+ initialWorkspaceDiskUsageRequested = true
7997
+ refresh_du()
7998
+ refresh_du("logs")
7999
+ }
8001
8000
 
8002
- let dynamic_loaded = false
8003
-
8004
- const try_dynamic = async () => {
8001
+ const try_dynamic = async (options = {}) => {
8005
8002
  let rendered
8006
8003
  let status
8007
8004
  const dynamicUrl = window.__pinokioDynamicUrl || "<%-dynamic%>"
@@ -8035,18 +8032,10 @@ const rerenderMenuSection = (container, html) => {
8035
8032
  rendered = false
8036
8033
  }
8037
8034
  if (rendered) {
8038
- // if (dynamic_loaded) {
8039
- // // already loaded, don't touch the UI
8040
- // } else {
8041
- // // first time being loaded
8042
- // dynamic_loaded = true
8043
- // if (document.querySelector(".dynamic .reveal")) {
8044
- // console.log("click")
8045
- //
8046
- //// document.querySelector(".dynamic .reveal").click()
8047
- // }
8048
- // }
8049
8035
  } else {
8036
+ if (options.retryOnEmpty === false) {
8037
+ return
8038
+ }
8050
8039
  setTimeout(() => {
8051
8040
  try_dynamic()
8052
8041
  }, 1000)
@@ -8100,10 +8089,11 @@ const rerenderMenuSection = (container, html) => {
8100
8089
  renderSelection()
8101
8090
  }
8102
8091
  }
8103
- let init = document.querySelector("[data-init]")
8104
- if (init) {
8105
- init.click()
8106
- }
8092
+ <% if (type === 'browse') { %>
8093
+ setTimeout(() => {
8094
+ try_dynamic()
8095
+ }, 0)
8096
+ <% } %>
8107
8097
  window.addEventListener('message', (event) => {
8108
8098
  // only process the event it's coming from pinokio
8109
8099
  let origin = event.origin
@@ -8161,6 +8151,8 @@ const rerenderMenuSection = (container, html) => {
8161
8151
  if (event.data.type === 'stream') {
8162
8152
  const frameName = resolveFrameName(null, event.source)
8163
8153
  updateTabTimestamp(frameName, Date.now())
8154
+ } else if (event.data.type === 'idle') {
8155
+ return
8164
8156
  } else if (event.data.type === 'restart') {
8165
8157
  <% if (type === 'run') { %>
8166
8158
  clearPersistedFrameLinkSelection()
@@ -8189,8 +8181,6 @@ const rerenderMenuSection = (container, html) => {
8189
8181
 
8190
8182
 
8191
8183
  });
8192
- refresh_du()
8193
- refresh_du("logs")
8194
8184
  renderSelection({ force: true })
8195
8185
  <% if (type === "browse" || type === "files") { %>
8196
8186
  const repoStatusCache = new Map()
@@ -12411,7 +12401,6 @@ document.addEventListener("DOMContentLoaded", () => {
12411
12401
  if (!normalized) {
12412
12402
  return false
12413
12403
  }
12414
- refreshTerminalSessions(normalized, workspaceCwd)
12415
12404
  pickerRequestId += 1
12416
12405
  currentUrl = normalized
12417
12406
  setLocation(normalized)
@@ -524,51 +524,7 @@ body.dark #update-spec {
524
524
 
525
525
  <div class='menu-grid <%= primaryColumnCount === 3 ? "menu-grid--triptych" : "" %>'>
526
526
  <% if (userTerminal && userTerminal.menu && userTerminal.menu.length) { %>
527
- <div class='menu-container menu-column user-terminal'>
528
- <div class='tab-header'>
529
- <h3><i class='<%= userTerminal.icon %>'></i> <%= userTerminal.title %></h3>
530
- </div>
531
- <% if (userTerminal.subtitle || userTerminal.subtitle_link_href) { %>
532
- <div class='column-subtitle'>
533
- <% if (userTerminal.subtitle) { %>
534
- <span><%= userTerminal.subtitle %></span><% if (userTerminal.subtitle_link_href && userTerminal.subtitle_link_label) { %> <% } %>
535
- <% } %>
536
- <% if (userTerminal.subtitle_link_href && userTerminal.subtitle_link_label) { %>
537
- <a class='column-subtitle-link' href="<%= userTerminal.subtitle_link_href %>" target="_parent"><%= userTerminal.subtitle_link_label %></a>
538
- <% } %>
539
- </div>
540
- <% } %>
541
- <div class='tab-content'>
542
- <% userTerminal.menu.forEach((i) => { %>
543
- <div class='tab' role="button" tabindex="0" data-index="<%= index++ %>" data-target="@<%= i.href %>" data-href="<%= i.href %>">
544
- <% if (i.image) { %>
545
- <img src="<%= i.image %>">
546
- <% } else if (i.icon) { %>
547
- <i class="img <%= i.icon %>"></i>
548
- <% } %>
549
- <div class='tab-copy'>
550
- <h2 class='tab-title'><%= i.title %></h2>
551
- <% if (i.subtitle) { %>
552
- <div class='tab-subtitle subtitle'><%= i.subtitle %></div>
553
- <% } %>
554
- </div>
555
- <div class='tab-actions'>
556
- <% if (i.link) { %>
557
- <button class="tab-action-link" type="button" data-doc-link="true" data-href="<%= i.link %>" title="Open docs" data-tippy-content="Open docs" aria-label="Open docs">
558
- <i class="fa-solid fa-circle-info"></i>
559
- </button>
560
- <% } %>
561
- <button class="ai-perm-link" type="button" data-ai-consent="<%= i.href %>" title="AI permissions" data-tippy-content="AI permissions" aria-label="AI permissions">
562
- <i class="fa-solid fa-shield-halved"></i>
563
- </button>
564
- <div class='disclosure-indicator' aria-hidden="true">
565
- <i class="fa-solid fa-chevron-right"></i>
566
- </div>
567
- </div>
568
- </div>
569
- <% }) %>
570
- </div>
571
- </div>
527
+ <%- include('./partials/d_terminal_column', { userTerminal, terminals_url }) %>
572
528
  <% } %>
573
529
 
574
530
  <% if (cliMenu && cliMenu.menu && cliMenu.menu.length) { %>
@@ -797,12 +753,16 @@ document.querySelector("#update-spec").addEventListener("click", (e) => {
797
753
  })
798
754
  */
799
755
  let list = []
800
- document.querySelectorAll(".tab").forEach((el, index) => {
801
- list.push({
802
- index: parseInt(el.getAttribute("data-index")),
803
- text: el.textContent,
756
+ const rebuildTabList = () => {
757
+ list = []
758
+ document.querySelectorAll(".tab").forEach((el, index) => {
759
+ el.setAttribute("data-index", String(index))
760
+ list.push({
761
+ index,
762
+ text: el.textContent,
763
+ })
804
764
  })
805
- })
765
+ }
806
766
  const search = (items, value) => {
807
767
  let filtered = []
808
768
  for(let i=0; i<items.length; i++) {
@@ -848,6 +808,7 @@ const renderSearch = () => {
848
808
  }
849
809
  }
850
810
  }
811
+ rebuildTabList()
851
812
  renderSearch()
852
813
  const workspaceCwd = (() => {
853
814
  const node = document.querySelector(".file-open")
@@ -973,6 +934,34 @@ document.querySelector("form input[type=search]").addEventListener("input", (e)
973
934
  renderSearch()
974
935
  })
975
936
  document.querySelector("form input[type=search]").focus()
937
+ const hydrateTerminalColumn = async () => {
938
+ const terminalColumn = document.querySelector("[data-dev-terminals-column][data-terminals-url]")
939
+ if (!terminalColumn) {
940
+ return
941
+ }
942
+ const url = terminalColumn.getAttribute("data-terminals-url")
943
+ if (!url) {
944
+ return
945
+ }
946
+ try {
947
+ const html = await fetch(url).then((res) => res.text())
948
+ if (!html || html.trim().length === 0) {
949
+ return
950
+ }
951
+ const wrapper = document.createElement("div")
952
+ wrapper.innerHTML = html
953
+ const nextColumn = wrapper.firstElementChild
954
+ if (!nextColumn) {
955
+ return
956
+ }
957
+ terminalColumn.replaceWith(nextColumn)
958
+ rebuildTabList()
959
+ renderSearch()
960
+ } catch (_) {}
961
+ }
962
+ setTimeout(() => {
963
+ hydrateTerminalColumn()
964
+ }, 0)
976
965
  document.addEventListener("click", (e) => {
977
966
  const permTarget = e.target.closest('[data-ai-consent]')
978
967
  if (permTarget) {
@@ -0,0 +1,46 @@
1
+ <div class='menu-container menu-column user-terminal' data-dev-terminals-column="true"<% if (terminals_url) { %> data-terminals-url="<%= terminals_url %>"<% } %>>
2
+ <div class='tab-header'>
3
+ <h3><i class='<%= userTerminal.icon %>'></i> <%= userTerminal.title %></h3>
4
+ </div>
5
+ <% if (userTerminal.subtitle || userTerminal.subtitle_link_href) { %>
6
+ <div class='column-subtitle'>
7
+ <% if (userTerminal.subtitle) { %>
8
+ <span><%= userTerminal.subtitle %></span><% if (userTerminal.subtitle_link_href && userTerminal.subtitle_link_label) { %> <% } %>
9
+ <% } %>
10
+ <% if (userTerminal.subtitle_link_href && userTerminal.subtitle_link_label) { %>
11
+ <a class='column-subtitle-link' href="<%= userTerminal.subtitle_link_href %>" target="_parent"><%= userTerminal.subtitle_link_label %></a>
12
+ <% } %>
13
+ </div>
14
+ <% } %>
15
+ <div class='tab-content'>
16
+ <% let localIndex = 0 %>
17
+ <% userTerminal.menu.forEach((i) => { %>
18
+ <div class='tab' role="button" tabindex="0" data-index="<%= localIndex++ %>" data-target="@<%= i.href %>" data-href="<%= i.href %>">
19
+ <% if (i.image) { %>
20
+ <img src="<%= i.image %>">
21
+ <% } else if (i.icon) { %>
22
+ <i class="img <%= i.icon %>"></i>
23
+ <% } %>
24
+ <div class='tab-copy'>
25
+ <h2 class='tab-title'><%= i.title %></h2>
26
+ <% if (i.subtitle) { %>
27
+ <div class='tab-subtitle subtitle'><%= i.subtitle %></div>
28
+ <% } %>
29
+ </div>
30
+ <div class='tab-actions'>
31
+ <% if (i.link) { %>
32
+ <button class="tab-action-link" type="button" data-doc-link="true" data-href="<%= i.link %>" title="Open docs" data-tippy-content="Open docs" aria-label="Open docs">
33
+ <i class="fa-solid fa-circle-info"></i>
34
+ </button>
35
+ <% } %>
36
+ <button class="ai-perm-link" type="button" data-ai-consent="<%= i.href %>" title="AI permissions" data-tippy-content="AI permissions" aria-label="AI permissions">
37
+ <i class="fa-solid fa-shield-halved"></i>
38
+ </button>
39
+ <div class='disclosure-indicator' aria-hidden="true">
40
+ <i class="fa-solid fa-chevron-right"></i>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ <% }) %>
45
+ </div>
46
+ </div>
@@ -552,6 +552,67 @@ const createRunControls = () => {
552
552
  }
553
553
  return { set }
554
554
  }
555
+ const PLUGIN_TERMINAL_IDLE_WINDOW_MS = 1200
556
+ const createPluginTerminalDiscoveryRefresher = (context = {}) => {
557
+ const enabled = (() => {
558
+ try {
559
+ return window.location.pathname.startsWith("/run/plugin/")
560
+ } catch (_) {
561
+ return false
562
+ }
563
+ })()
564
+ let idleTimer = null
565
+ let armed = false
566
+ let sawStream = false
567
+ const clearIdleTimer = () => {
568
+ if (idleTimer) {
569
+ clearTimeout(idleTimer)
570
+ idleTimer = null
571
+ }
572
+ }
573
+ const refresh = () => {
574
+ if (!enabled || !armed || !sawStream) {
575
+ return
576
+ }
577
+ armed = false
578
+ sawStream = false
579
+ try {
580
+ const discovery = window.PinokioTerminalsDiscovery
581
+ if (!discovery || typeof discovery.refreshTerminalSessions !== "function") {
582
+ return
583
+ }
584
+ discovery.refreshTerminalSessions(window.location.href, context.cwd || "", {
585
+ retryDelays: []
586
+ })
587
+ } catch (_) {}
588
+ }
589
+ return {
590
+ arm(meaningful) {
591
+ if (!enabled || !meaningful) {
592
+ return
593
+ }
594
+ armed = true
595
+ sawStream = false
596
+ clearIdleTimer()
597
+ },
598
+ markActivity() {
599
+ if (!enabled || !armed) {
600
+ return
601
+ }
602
+ sawStream = true
603
+ clearIdleTimer()
604
+ idleTimer = setTimeout(() => {
605
+ idleTimer = null
606
+ refresh()
607
+ }, PLUGIN_TERMINAL_IDLE_WINDOW_MS)
608
+ },
609
+ clear() {
610
+ armed = false
611
+ sawStream = false
612
+ clearIdleTimer()
613
+ }
614
+ }
615
+ }
555
616
  const createAiConsentManager = (context = {}) => {
556
617
  const storage = getSafeLocalStorage()
557
618
  const storageSupported = !!storage
@@ -848,6 +909,9 @@ document.addEventListener("DOMContentLoaded", async () => {
848
909
  uri: scriptUri || ("~" + location.pathname)
849
910
  })
850
911
  const runControls = createRunControls()
912
+ const pluginTerminalDiscoveryRefresher = createPluginTerminalDiscoveryRefresher({
913
+ cwd: scriptCwd
914
+ })
851
915
  consentManager.showChipIfRemembered()
852
916
  class RPC {
853
917
  constructor() {
@@ -923,16 +987,17 @@ document.addEventListener("DOMContentLoaded", async () => {
923
987
  }
924
988
  }
925
989
  notifyLineSubmitted(line, meta = {}) {
926
- if (this.inputTracker) {
927
- this.inputTracker.submit(line, meta)
928
- return
929
- }
930
990
  const safeLine = sanitizePreviewLine(line || "")
931
991
  const preview = safeLine.trim()
932
992
  const limit = 200
933
993
  const truncated = preview.length > limit ? preview.slice(0, limit) + "..." : preview
934
994
  const hadLineBreak = Boolean(meta && meta.hadLineBreak)
935
995
  const meaningful = truncated.length > 0 || hadLineBreak
996
+ pluginTerminalDiscoveryRefresher.arm(meaningful)
997
+ if (this.inputTracker) {
998
+ this.inputTracker.submit(line, meta)
999
+ return
1000
+ }
936
1001
  postMessageToAncestors({
937
1002
  type: "terminal-input",
938
1003
  frame: window.name || null,
@@ -1096,6 +1161,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1096
1161
  }
1097
1162
  } else if (packet.type === "stream") {
1098
1163
  refreshParent(packet)
1164
+ pluginTerminalDiscoveryRefresher.markActivity()
1099
1165
  // set the current shell id
1100
1166
  const previousShellId = shell_id
1101
1167
  if (packet.data.id) {
@@ -1128,6 +1194,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1128
1194
  runControls.set("running")
1129
1195
  } else if (packet.type === 'disconnect') {
1130
1196
  refreshParent(packet)
1197
+ pluginTerminalDiscoveryRefresher.clear()
1131
1198
  reloadMemory()
1132
1199
  this.term.write("\r\nDisconnected...\r\n")
1133
1200
  document.querySelector("#status-window").innerHTML = "<b>Ready</b>"
@@ -1423,6 +1490,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1423
1490
  n.Noty(payload)
1424
1491
  }
1425
1492
  } else if (packet.type === "restart") {
1493
+ pluginTerminalDiscoveryRefresher.clear()
1426
1494
  try {
1427
1495
  if (window.parent && window.parent !== window && typeof window.parent.postMessage === "function") {
1428
1496
  window.parent.postMessage(packet, "*")
@@ -1469,6 +1537,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1469
1537
  text: `${packet.data}`,
1470
1538
  })
1471
1539
  } else if (packet.type === "error") {
1540
+ pluginTerminalDiscoveryRefresher.clear()
1472
1541
  runControls.set("idle")
1473
1542
 
1474
1543
 
@@ -1547,6 +1616,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1547
1616
  type: 'success'
1548
1617
  })
1549
1618
  */
1619
+ pluginTerminalDiscoveryRefresher.clear()
1550
1620
  runControls.set("idle")
1551
1621
  }, 0)
1552
1622
  //this.socket.close()
@@ -1575,6 +1645,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1575
1645
  this.mode = (mode ? mode : "run")
1576
1646
  const allowed = aiConsentRequired ? await consentManager.ensureAllowed() : true
1577
1647
  if (!allowed) {
1648
+ pluginTerminalDiscoveryRefresher.clear()
1578
1649
  runControls.set("idle")
1579
1650
  n.Noty({
1580
1651
  text: "Run canceled; AI agents remain blocked for this folder.",
@@ -1601,6 +1672,7 @@ document.addEventListener("DOMContentLoaded", async () => {
1601
1672
  await this.start(mode)
1602
1673
  return true
1603
1674
  } catch (error) {
1675
+ pluginTerminalDiscoveryRefresher.clear()
1604
1676
  runControls.set("idle")
1605
1677
  const message = error && error.message ? error.message : "Failed to start"
1606
1678
  n.Noty({