pinokiod 6.0.72 → 6.0.73

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "6.0.72",
3
+ "version": "6.0.73",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -67,7 +67,7 @@ const Setup = require("../kernel/bin/setup")
67
67
  const { createTerminalSessionHelpers } = require("./lib/terminal_session_helpers")
68
68
  const { createTerminalGitResetHandler } = require("./lib/terminal_git_reset")
69
69
  const { createDesktopEventRouter } = require("./lib/desktop_event_router")
70
- const { createInjectRouter, normalizeInjectHrefList } = require("./lib/inject_router")
70
+ const { createInjectRouter, normalizeInjectList } = require("./lib/inject_router")
71
71
  const AppRegistryService = require("./lib/app_registry")
72
72
  const AppLogService = require("./lib/app_logs")
73
73
  const AppSearchService = require("./lib/app_search")
@@ -3539,40 +3539,6 @@ class Server {
3539
3539
 
3540
3540
  async renderMenu(req, uri, name, config, pathComponents, indexPath) {
3541
3541
  if (config.menu) {
3542
- const appendInjectQueryParam = (href, injectList) => {
3543
- if (typeof href !== "string") {
3544
- return href
3545
- }
3546
- const trimmedHref = href.trim()
3547
- if (!trimmedHref || !injectList.length) {
3548
- return href
3549
- }
3550
- let parsed
3551
- let isAbsolute = false
3552
- try {
3553
- if (/^[a-zA-Z][a-zA-Z\d+\-.]*:/.test(trimmedHref)) {
3554
- isAbsolute = true
3555
- parsed = new URL(trimmedHref)
3556
- } else {
3557
- parsed = new URL(trimmedHref, "http://localhost")
3558
- }
3559
- } catch (_) {
3560
- return href
3561
- }
3562
- const seen = new Set(parsed.searchParams.getAll("__pinokio_inject"))
3563
- for (const entry of injectList) {
3564
- if (seen.has(entry)) {
3565
- continue
3566
- }
3567
- seen.add(entry)
3568
- parsed.searchParams.append("__pinokio_inject", entry)
3569
- }
3570
- if (isAbsolute) {
3571
- return parsed.toString()
3572
- }
3573
- return `${parsed.pathname}${parsed.search}${parsed.hash}`
3574
- }
3575
-
3576
3542
  // config.menu = [{
3577
3543
  // base: "/",
3578
3544
  // text: "Configure",
@@ -3663,13 +3629,13 @@ class Server {
3663
3629
  if (menuitem.href && menuitem.params) {
3664
3630
  menuitem.href = menuitem.href + "?" + new URLSearchParams(menuitem.params).toString();
3665
3631
  }
3666
- if (menuitem.href) {
3667
- const injectList = normalizeInjectHrefList(menuitem.inject)
3668
- if (injectList.length > 0) {
3669
- const hrefWithInject = appendInjectQueryParam(menuitem.href, injectList)
3670
- menuitem.href = hrefWithInject
3671
- config.menu[i].href = hrefWithInject
3672
- }
3632
+ const injectList = normalizeInjectList(menuitem.inject)
3633
+ if (injectList.length > 0) {
3634
+ menuitem.inject = injectList
3635
+ config.menu[i].inject = injectList
3636
+ } else if (menuitem.inject) {
3637
+ delete menuitem.inject
3638
+ delete config.menu[i].inject
3673
3639
  }
3674
3640
 
3675
3641
 
@@ -5970,7 +5936,7 @@ class Server {
5970
5936
  }
5971
5937
  res.json({ ok: !!ok, meta })
5972
5938
  }))
5973
- this.app.get("/agents", ex(async (req, res) => {
5939
+ this.app.get("/plugins", ex(async (req, res) => {
5974
5940
  let pluginMenu = []
5975
5941
  try {
5976
5942
  if (!this.kernel.plugin.config) {
@@ -6049,7 +6015,7 @@ class Server {
6049
6015
 
6050
6016
  const peerAccess = await this.composePeerAccessPayload()
6051
6017
  const list = this.getPeers()
6052
- res.render("agents", {
6018
+ res.render("plugins", {
6053
6019
  current_host: this.kernel.peer.host,
6054
6020
  ...peerAccess,
6055
6021
  pluginMenu,
@@ -6075,11 +6041,8 @@ class Server {
6075
6041
  res.json({ menu: [] })
6076
6042
  }
6077
6043
  }))
6078
- this.app.get("/plugins", (req, res) => {
6079
- res.redirect(301, "/agents")
6080
- })
6081
6044
  this.app.get("/terminals", (req, res) => {
6082
- res.redirect(301, "/agents")
6045
+ res.redirect(301, "/home?mode=terminals")
6083
6046
  })
6084
6047
  this.app.get("/screenshots", ex(async (req, res) => {
6085
6048
  const peerAccess = await this.composePeerAccessPayload()
@@ -10481,14 +10444,14 @@ class Server {
10481
10444
  let dynamic = [
10482
10445
  terminal,
10483
10446
  {
10484
- icon: "fa-solid fa-robot",
10485
- title: "Terminal Agents",
10447
+ icon: "fa-solid fa-plug-circle-bolt",
10448
+ title: "Terminal Plugins",
10486
10449
  subtitle: "Start a session in Pinokio",
10487
10450
  menu: shell_menus
10488
10451
  },
10489
10452
  {
10490
10453
  icon: "fa-solid fa-arrow-up-right-from-square",
10491
- title: "Desktop App Agents",
10454
+ title: "Desktop Plugins",
10492
10455
  subtitle: "Open the project in external desktop apps",
10493
10456
  menu: exec_menus
10494
10457
  },
@@ -10592,73 +10555,6 @@ class Server {
10592
10555
  const runPath = typeof req.params[0] === "string" ? req.params[0] : ""
10593
10556
  let pathComponents = runPath.split("/")
10594
10557
  req.base = this.kernel.homedir
10595
- const readQueryValue = (value) => {
10596
- if (Array.isArray(value)) {
10597
- const first = value[0]
10598
- if (typeof first === "string") {
10599
- return first.trim()
10600
- }
10601
- if (typeof first === "number" || typeof first === "boolean") {
10602
- return String(first).trim()
10603
- }
10604
- return ""
10605
- }
10606
- if (typeof value === "string") {
10607
- return value.trim()
10608
- }
10609
- if (typeof value === "number" || typeof value === "boolean") {
10610
- return String(value).trim()
10611
- }
10612
- return ""
10613
- }
10614
- const normalizedRunPath = runPath.replace(/^\/+/, "").split("?")[0].toLowerCase()
10615
- const providerByPath = {
10616
- "plugin/code/codex/pinokio.js": "codex",
10617
- "plugin/code/claude/pinokio.js": "claude",
10618
- "plugin/code/gemini/pinokio.js": "gemini"
10619
- }
10620
- const pluginProvider = providerByPath[normalizedRunPath] || ""
10621
- const promptValue = readQueryValue(req.query ? req.query.prompt : "")
10622
- const terminalIdValue = readQueryValue(req.query ? req.query.terminal_id : "")
10623
- const workspacePath = readQueryValue(req.query ? req.query.cwd : "")
10624
- let refererIsDev = false
10625
- const referer = req.get("referer")
10626
- if (typeof referer === "string" && referer.length > 0) {
10627
- try {
10628
- const refererUrl = new URL(referer)
10629
- refererIsDev = /^\/p\/[^/]+\/dev(?:$|\/)/.test(refererUrl.pathname || "")
10630
- } catch (_) {
10631
- refererIsDev = false
10632
- }
10633
- }
10634
- const shouldRewriteToManagedStart = Boolean(
10635
- pluginProvider &&
10636
- refererIsDev &&
10637
- workspacePath &&
10638
- !promptValue &&
10639
- !terminalIdValue
10640
- )
10641
- if (shouldRewriteToManagedStart) {
10642
- try {
10643
- const payload = await createManagedTerminalSession({
10644
- provider: pluginProvider,
10645
- workspacePath
10646
- })
10647
- if (payload && typeof payload.url === "string" && payload.url.length > 0) {
10648
- res.redirect(payload.url)
10649
- return
10650
- }
10651
- } catch (error) {
10652
- console.warn("[run-plugin-managed-dev] managed start error, fallback to legacy", {
10653
- provider: pluginProvider,
10654
- error: error && error.message ? error.message : error
10655
- })
10656
- }
10657
- }
10658
- // Strip dev-only marker so legacy /run semantics remain unchanged outside rewrite path.
10659
- if (req.query && Object.prototype.hasOwnProperty.call(req.query, "managed_dev")) {
10660
- delete req.query.managed_dev
10661
- }
10662
10558
  try {
10663
10559
  await this.render(req, res, pathComponents)
10664
10560
  } catch (e) {
@@ -5,7 +5,11 @@ const path = require("path")
5
5
 
6
6
  const { resolveDesktopEventWorkspace } = require("./desktop_event_router")
7
7
 
8
- const normalizeInjectHrefList = (value) => {
8
+ const VALID_INJECT_WORLDS = new Set(["main", "isolated"])
9
+ const VALID_INJECT_WHEN = new Set(["start", "end", "idle"])
10
+ const VALID_INJECT_FRAMES = new Set(["top", "all"])
11
+
12
+ const normalizeInjectMatchList = (value) => {
9
13
  if (!value) {
10
14
  return []
11
15
  }
@@ -35,6 +39,93 @@ const normalizeInjectHrefList = (value) => {
35
39
  return normalized
36
40
  }
37
41
 
42
+ const normalizeInjectHrefList = (value) => {
43
+ return normalizeInjectList(value).map((entry) => entry.src)
44
+ }
45
+
46
+ const normalizeInjectEntry = (value) => {
47
+ let src = ""
48
+ let match = []
49
+ let world = "main"
50
+ let when = "idle"
51
+ let frame = "top"
52
+
53
+ if (typeof value === "string") {
54
+ src = value.trim()
55
+ } else if (value && typeof value === "object" && !Array.isArray(value)) {
56
+ src = typeof value.src === "string" ? value.src.trim() : ""
57
+ match = normalizeInjectMatchList(value.match)
58
+ if (typeof value.world === "string") {
59
+ const normalizedWorld = value.world.trim().toLowerCase()
60
+ if (VALID_INJECT_WORLDS.has(normalizedWorld)) {
61
+ world = normalizedWorld
62
+ }
63
+ }
64
+ if (typeof value.when === "string") {
65
+ const normalizedWhen = value.when.trim().toLowerCase()
66
+ if (VALID_INJECT_WHEN.has(normalizedWhen)) {
67
+ when = normalizedWhen
68
+ }
69
+ }
70
+ if (typeof value.frame === "string") {
71
+ const normalizedFrame = value.frame.trim().toLowerCase()
72
+ if (VALID_INJECT_FRAMES.has(normalizedFrame)) {
73
+ frame = normalizedFrame
74
+ }
75
+ }
76
+ }
77
+
78
+ if (!src || src.length > 1024) {
79
+ return null
80
+ }
81
+
82
+ return {
83
+ src,
84
+ match: match.length > 0 ? match : ["*"],
85
+ world,
86
+ when,
87
+ frame
88
+ }
89
+ }
90
+
91
+ const normalizeInjectList = (value) => {
92
+ if (!value) {
93
+ return []
94
+ }
95
+ const values = Array.isArray(value) ? value : [value]
96
+ const normalized = []
97
+ const seen = new Set()
98
+ for (const entry of values) {
99
+ if (typeof entry === "string") {
100
+ const parts = entry.split(",").map((item) => item.trim()).filter(Boolean)
101
+ for (const part of parts) {
102
+ const normalizedEntry = normalizeInjectEntry(part)
103
+ if (!normalizedEntry) {
104
+ continue
105
+ }
106
+ const signature = JSON.stringify(normalizedEntry)
107
+ if (seen.has(signature)) {
108
+ continue
109
+ }
110
+ seen.add(signature)
111
+ normalized.push(normalizedEntry)
112
+ }
113
+ continue
114
+ }
115
+ const normalizedEntry = normalizeInjectEntry(entry)
116
+ if (!normalizedEntry) {
117
+ continue
118
+ }
119
+ const signature = JSON.stringify(normalizedEntry)
120
+ if (seen.has(signature)) {
121
+ continue
122
+ }
123
+ seen.add(signature)
124
+ normalized.push(normalizedEntry)
125
+ }
126
+ return normalized
127
+ }
128
+
38
129
  const parseFrameInjectEntries = (frameUrl) => {
39
130
  if (typeof frameUrl !== "string") {
40
131
  return []
@@ -50,7 +141,7 @@ const parseFrameInjectEntries = (frameUrl) => {
50
141
  return []
51
142
  }
52
143
  const entries = parsed.searchParams.getAll("__pinokio_inject")
53
- return normalizeInjectHrefList(entries)
144
+ return normalizeInjectList(entries)
54
145
  }
55
146
 
56
147
  const resolveInjectLaunchUrl = async ({ workspace, workspaceRoot, launcher, hrefRaw }) => {
@@ -108,6 +199,25 @@ const resolveInjectLaunchUrl = async ({ workspace, workspaceRoot, launcher, href
108
199
  return queryString ? `${launchPath}?${queryString}` : launchPath
109
200
  }
110
201
 
202
+ const resolveInjectDescriptor = async ({ workspace, workspaceRoot, launcher, descriptor }) => {
203
+ const resolvedSrc = await resolveInjectLaunchUrl({
204
+ workspace,
205
+ workspaceRoot,
206
+ launcher,
207
+ hrefRaw: descriptor.src
208
+ })
209
+ if (!resolvedSrc) {
210
+ return null
211
+ }
212
+ return {
213
+ src: resolvedSrc,
214
+ match: Array.isArray(descriptor.match) ? descriptor.match.slice() : ["*"],
215
+ world: descriptor.world || "main",
216
+ when: descriptor.when || "idle",
217
+ frame: descriptor.frame || "top"
218
+ }
219
+ }
220
+
111
221
  const createInjectRouter = ({ kernel }) => {
112
222
  const handle = async (body = {}) => {
113
223
  const context = body && body.context && typeof body.context === "object" ? body.context : {}
@@ -159,44 +269,59 @@ const createInjectRouter = ({ kernel }) => {
159
269
  }
160
270
  }
161
271
 
272
+ const requestInjectEntries = normalizeInjectList(body && body.inject)
162
273
  const frameUrl = typeof context.frameUrl === "string" ? context.frameUrl : ""
163
- const frameInjectEntries = parseFrameInjectEntries(frameUrl)
164
- if (frameInjectEntries.length === 0) {
274
+ const injectEntries = requestInjectEntries.length > 0
275
+ ? requestInjectEntries
276
+ : parseFrameInjectEntries(frameUrl)
277
+ if (injectEntries.length === 0) {
165
278
  return {
166
279
  status: 200,
167
280
  body: {
168
281
  ok: true,
169
282
  matched: false,
170
283
  workspace,
284
+ inject: [],
171
285
  scripts: [],
172
286
  reason: "inject_not_requested"
173
287
  }
174
288
  }
175
289
  }
176
290
 
177
- const scripts = []
178
- for (const hrefRaw of frameInjectEntries) {
179
- const launchUrl = await resolveInjectLaunchUrl({
291
+ const inject = []
292
+ for (const descriptor of injectEntries) {
293
+ const resolvedDescriptor = await resolveInjectDescriptor({
180
294
  workspace,
181
295
  workspaceRoot,
182
296
  launcher,
183
- hrefRaw
297
+ descriptor
184
298
  })
185
- if (!launchUrl) {
299
+ if (!resolvedDescriptor) {
186
300
  continue
187
301
  }
188
- scripts.push(launchUrl)
302
+ inject.push(resolvedDescriptor)
189
303
  }
190
304
 
191
- const uniqueScripts = [...new Set(scripts)]
305
+ const uniqueInject = []
306
+ const seen = new Set()
307
+ for (const descriptor of inject) {
308
+ const signature = JSON.stringify(descriptor)
309
+ if (seen.has(signature)) {
310
+ continue
311
+ }
312
+ seen.add(signature)
313
+ uniqueInject.push(descriptor)
314
+ }
315
+ const scripts = uniqueInject.map((descriptor) => descriptor.src)
192
316
  return {
193
317
  status: 200,
194
318
  body: {
195
319
  ok: true,
196
- matched: uniqueScripts.length > 0,
320
+ matched: uniqueInject.length > 0,
197
321
  workspace,
198
- scripts: uniqueScripts,
199
- source: "frame_query"
322
+ inject: uniqueInject,
323
+ scripts,
324
+ source: requestInjectEntries.length > 0 ? "request_body" : "frame_query"
200
325
  }
201
326
  }
202
327
  }
@@ -208,5 +333,6 @@ const createInjectRouter = ({ kernel }) => {
208
333
 
209
334
  module.exports = {
210
335
  createInjectRouter,
211
- normalizeInjectHrefList
336
+ normalizeInjectHrefList,
337
+ normalizeInjectList
212
338
  }
@@ -23,7 +23,7 @@ const guardedRoutePrefixes = [
23
23
  '/github',
24
24
  '/setup/',
25
25
  '/requirements_check/',
26
- '/agents',
26
+ '/plugins',
27
27
  '/network',
28
28
  '/net/',
29
29
  '/git/',
@@ -3692,7 +3692,7 @@ document.addEventListener("DOMContentLoaded", () => {
3692
3692
  return mapped.length > 0 ? mapped : ASK_AI_FALLBACK_TOOLS.slice();
3693
3693
  })
3694
3694
  .catch((error) => {
3695
- console.warn('Failed to load Ask AI agents, using fallback list', error);
3695
+ console.warn('Failed to load Ask AI plugins, using fallback list', error);
3696
3696
  return ASK_AI_FALLBACK_TOOLS.slice();
3697
3697
  })
3698
3698
  .finally(() => {
@@ -3703,52 +3703,136 @@ document.addEventListener("DOMContentLoaded", () => {
3703
3703
  return tools;
3704
3704
  }
3705
3705
 
3706
- function resolveAskAiCliProvider(agentHref) {
3707
- if (!agentHref) {
3708
- return '';
3706
+ const TERMINALS_DISCOVERY_REFRESH_SIGNAL_KEY = 'pinokio.terminals.discovery.refresh';
3707
+ let terminalsDiscoveryBroadcastChannel = null;
3708
+ const terminalsDiscoveryRefreshState = {
3709
+ lastAtByWorkspace: new Map()
3710
+ };
3711
+
3712
+ function normalizeWorkspaceCwdForTerminalsDiscovery(value) {
3713
+ return typeof value === 'string' ? value.trim() : '';
3714
+ }
3715
+
3716
+ function getTerminalsDiscoveryBroadcastChannel() {
3717
+ if (terminalsDiscoveryBroadcastChannel !== null) {
3718
+ return terminalsDiscoveryBroadcastChannel;
3719
+ }
3720
+ if (typeof window.BroadcastChannel !== 'function') {
3721
+ terminalsDiscoveryBroadcastChannel = false;
3722
+ return null;
3709
3723
  }
3710
3724
  try {
3711
- const parsed = new URL(agentHref, window.location.origin);
3712
- const match = /^\/run\/plugin\/code\/(codex|claude|gemini)\/pinokio\.js$/i.exec(parsed.pathname || '');
3713
- if (!match || !match[1]) {
3714
- return '';
3715
- }
3716
- return String(match[1]).toLowerCase();
3725
+ terminalsDiscoveryBroadcastChannel = new window.BroadcastChannel(TERMINALS_DISCOVERY_REFRESH_SIGNAL_KEY);
3726
+ return terminalsDiscoveryBroadcastChannel;
3717
3727
  } catch (_) {
3718
- return '';
3728
+ terminalsDiscoveryBroadcastChannel = false;
3729
+ return null;
3719
3730
  }
3720
3731
  }
3721
3732
 
3722
- async function startAskAiCliSession(provider, workspaceCwd) {
3723
- const response = await fetch('/terminals/start', {
3724
- method: 'POST',
3725
- headers: {
3726
- 'Content-Type': 'application/json'
3727
- },
3728
- body: JSON.stringify({
3729
- provider,
3730
- workspacePath: workspaceCwd || ''
3731
- })
3732
- });
3733
- let payload = null;
3733
+ function extractWorkspaceCwdFromPluginLaunchUrl(rawUrl, workspaceCwd = '') {
3734
+ const fallbackCwd = normalizeWorkspaceCwdForTerminalsDiscovery(workspaceCwd);
3735
+ if (!rawUrl) {
3736
+ return fallbackCwd;
3737
+ }
3734
3738
  try {
3735
- payload = await response.json();
3739
+ const parsed = new URL(rawUrl, window.location.origin);
3740
+ if (parsed.origin !== window.location.origin) {
3741
+ return fallbackCwd;
3742
+ }
3743
+ if (!parsed.pathname.startsWith('/run/plugin/')) {
3744
+ return '';
3745
+ }
3746
+ const launchCwd = normalizeWorkspaceCwdForTerminalsDiscovery(parsed.searchParams.get('cwd') || '');
3747
+ return launchCwd || fallbackCwd;
3736
3748
  } catch (_) {
3737
- payload = null;
3749
+ return fallbackCwd;
3738
3750
  }
3739
- if (!response.ok || !payload || !payload.url) {
3740
- const message = payload && payload.error ? payload.error : `Failed to start ${provider}`;
3741
- throw new Error(message);
3751
+ }
3752
+
3753
+ function buildTerminalsDiscoveryRefreshUrl(workspaceCwd = '') {
3754
+ const params = new URLSearchParams();
3755
+ params.set('mode', 'terminals');
3756
+ params.set('fetch', '1');
3757
+ params.set('sync', '1');
3758
+ params.set('limit', '1');
3759
+ const normalizedWorkspace = normalizeWorkspaceCwdForTerminalsDiscovery(workspaceCwd);
3760
+ if (normalizedWorkspace) {
3761
+ params.set('workspace', normalizedWorkspace);
3742
3762
  }
3763
+ return `/home?${params.toString()}`;
3764
+ }
3765
+
3766
+ function dispatchTerminalsDiscoveryRefreshSignal(workspaceCwd = '') {
3767
+ const payload = {
3768
+ workspaceCwd: normalizeWorkspaceCwdForTerminalsDiscovery(workspaceCwd),
3769
+ ts: Date.now()
3770
+ };
3743
3771
  try {
3744
- const parsed = new URL(payload.url, window.location.origin);
3745
- parsed.searchParams.set('ask_ai', '1');
3746
- return `${parsed.pathname}${parsed.search}${parsed.hash}`;
3747
- } catch (_) {
3748
- return payload.url;
3772
+ window.localStorage.setItem(TERMINALS_DISCOVERY_REFRESH_SIGNAL_KEY, JSON.stringify(payload));
3773
+ } catch (_) {}
3774
+ const channel = getTerminalsDiscoveryBroadcastChannel();
3775
+ if (channel) {
3776
+ try {
3777
+ channel.postMessage(payload);
3778
+ } catch (_) {}
3779
+ }
3780
+ return payload;
3781
+ }
3782
+
3783
+ function requestTerminalsDiscoveryRefresh(workspaceCwd = '', options = {}) {
3784
+ const normalizedWorkspace = normalizeWorkspaceCwdForTerminalsDiscovery(workspaceCwd);
3785
+ if (!normalizedWorkspace) {
3786
+ return false;
3787
+ }
3788
+ const now = Date.now();
3789
+ const dedupeWindowMs = Number.isFinite(options && options.dedupeWindowMs)
3790
+ ? Math.max(0, Math.floor(options.dedupeWindowMs))
3791
+ : 1200;
3792
+ const lastAt = terminalsDiscoveryRefreshState.lastAtByWorkspace.get(normalizedWorkspace) || 0;
3793
+ if (now - lastAt < dedupeWindowMs) {
3794
+ return false;
3795
+ }
3796
+ terminalsDiscoveryRefreshState.lastAtByWorkspace.set(normalizedWorkspace, now);
3797
+
3798
+ const requestRefresh = () => {
3799
+ dispatchTerminalsDiscoveryRefreshSignal(normalizedWorkspace);
3800
+ const refreshUrl = buildTerminalsDiscoveryRefreshUrl(normalizedWorkspace);
3801
+ fetch(refreshUrl, {
3802
+ method: 'GET',
3803
+ keepalive: true,
3804
+ cache: 'no-store',
3805
+ headers: {
3806
+ Accept: 'application/json'
3807
+ }
3808
+ }).catch(() => {});
3809
+ };
3810
+
3811
+ requestRefresh();
3812
+ const retryDelays = Array.isArray(options && options.retryDelays) && options.retryDelays.length > 0
3813
+ ? options.retryDelays
3814
+ : [2500];
3815
+ retryDelays.forEach((delay) => {
3816
+ const safeDelay = Number.isFinite(delay) ? Math.max(0, Math.floor(delay)) : 0;
3817
+ window.setTimeout(requestRefresh, safeDelay);
3818
+ });
3819
+ return true;
3820
+ }
3821
+
3822
+ function refreshTerminalSessions(rawUrl, workspaceCwd = '', options = {}) {
3823
+ const resolvedWorkspace = extractWorkspaceCwdFromPluginLaunchUrl(rawUrl, workspaceCwd);
3824
+ if (!resolvedWorkspace) {
3825
+ return false;
3749
3826
  }
3827
+ return requestTerminalsDiscoveryRefresh(resolvedWorkspace, options);
3750
3828
  }
3751
3829
 
3830
+ window.PinokioTerminalsDiscovery = Object.assign({}, window.PinokioTerminalsDiscovery, {
3831
+ buildRefreshUrl: buildTerminalsDiscoveryRefreshUrl,
3832
+ requestRefresh: requestTerminalsDiscoveryRefresh,
3833
+ refreshTerminalSessions
3834
+ });
3835
+
3752
3836
  function buildAskAiLaunchUrl(agentHref, workspaceCwd) {
3753
3837
  let next = agentHref || '';
3754
3838
  try {
@@ -3842,19 +3926,11 @@ document.addEventListener("DOMContentLoaded", () => {
3842
3926
  if (dispatchAskAiLaunch(payload)) {
3843
3927
  return;
3844
3928
  }
3845
- const cliProvider = resolveAskAiCliProvider(tool.href);
3846
- if (cliProvider) {
3847
- startAskAiCliSession(cliProvider, workspaceCwd).then((sessionUrl) => {
3848
- if (sessionUrl) {
3849
- window.location.href = sessionUrl;
3850
- }
3851
- }).catch((error) => {
3852
- console.warn('Failed to start Ask AI CLI session', error);
3853
- });
3854
- return;
3855
- }
3856
3929
  const fallbackUrl = buildAskAiLaunchUrl(tool.href, workspaceCwd);
3857
3930
  if (fallbackUrl) {
3931
+ refreshTerminalSessions(fallbackUrl, workspaceCwd, {
3932
+ retryDelays: []
3933
+ });
3858
3934
  window.location.href = fallbackUrl;
3859
3935
  }
3860
3936
  }