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 +1 -1
- package/server/index.js +14 -118
- package/server/lib/inject_router.js +141 -15
- package/server/public/common.js +121 -45
- package/server/public/create-launcher.js +22 -12
- package/server/public/install.js +1 -1
- package/server/public/terminal-settings.js +1 -3
- package/server/public/urldropdown.css +46 -7
- package/server/views/app.ejs +198 -168
- package/server/views/d.ejs +35 -7
- package/server/views/partials/main_sidebar.ejs +1 -1
- package/server/views/partials/menu.ejs +2 -2
- package/server/views/{agents.ejs → plugins.ejs} +66 -66
- package/server/views/terminals.ejs +132 -1
package/package.json
CHANGED
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,
|
|
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
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
3671
|
-
|
|
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("/
|
|
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("
|
|
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, "/
|
|
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-
|
|
10485
|
-
title: "Terminal
|
|
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
|
|
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
|
|
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
|
|
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
|
|
164
|
-
|
|
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
|
|
178
|
-
for (const
|
|
179
|
-
const
|
|
291
|
+
const inject = []
|
|
292
|
+
for (const descriptor of injectEntries) {
|
|
293
|
+
const resolvedDescriptor = await resolveInjectDescriptor({
|
|
180
294
|
workspace,
|
|
181
295
|
workspaceRoot,
|
|
182
296
|
launcher,
|
|
183
|
-
|
|
297
|
+
descriptor
|
|
184
298
|
})
|
|
185
|
-
if (!
|
|
299
|
+
if (!resolvedDescriptor) {
|
|
186
300
|
continue
|
|
187
301
|
}
|
|
188
|
-
|
|
302
|
+
inject.push(resolvedDescriptor)
|
|
189
303
|
}
|
|
190
304
|
|
|
191
|
-
const
|
|
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:
|
|
320
|
+
matched: uniqueInject.length > 0,
|
|
197
321
|
workspace,
|
|
198
|
-
|
|
199
|
-
|
|
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
|
}
|
package/server/public/common.js
CHANGED
|
@@ -23,7 +23,7 @@ const guardedRoutePrefixes = [
|
|
|
23
23
|
'/github',
|
|
24
24
|
'/setup/',
|
|
25
25
|
'/requirements_check/',
|
|
26
|
-
'/
|
|
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
|
|
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
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
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
|
-
|
|
3712
|
-
|
|
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
|
-
|
|
3728
|
+
terminalsDiscoveryBroadcastChannel = false;
|
|
3729
|
+
return null;
|
|
3719
3730
|
}
|
|
3720
3731
|
}
|
|
3721
3732
|
|
|
3722
|
-
|
|
3723
|
-
const
|
|
3724
|
-
|
|
3725
|
-
|
|
3726
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3749
|
+
return fallbackCwd;
|
|
3738
3750
|
}
|
|
3739
|
-
|
|
3740
|
-
|
|
3741
|
-
|
|
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
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
|
|
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
|
}
|