pinokiod 7.1.72 → 7.1.74
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/api/script/index.js +10 -8
- package/kernel/api/shell/index.js +32 -7
- package/kernel/bin/bluefairy.js +76 -11
- package/kernel/shell.js +55 -0
- package/package.json +1 -1
- package/server/index.js +192 -70
- package/server/public/task-launcher.css +265 -9
- package/server/public/universal-launcher.js +23 -28
- package/server/socket.js +5 -1
- package/server/views/app.ejs +216 -2
- package/server/views/checkpoints.ejs +1 -1
- package/server/views/d.ejs +1 -1
- package/server/views/index.ejs +28 -1
- package/server/views/launch.ejs +1 -1
- package/server/views/net.ejs +1 -1
- package/server/views/network.ejs +1 -1
- package/server/views/pro.ejs +1 -1
- package/server/views/screenshots.ejs +1 -1
- package/server/views/setup.ejs +376 -402
- package/server/views/shell.ejs +1 -1
- package/server/views/task_launch.ejs +6 -2
- package/server/views/task_list.ejs +1 -1
- package/server/views/terminal.ejs +6 -6
- package/test-results/after-conda-launch.png +0 -0
- package/test-results/dev-page.png +0 -0
|
@@ -13,13 +13,13 @@ class Script {
|
|
|
13
13
|
return null
|
|
14
14
|
}
|
|
15
15
|
currentSessionId(req) {
|
|
16
|
-
if (req && typeof req.id === "string" && req.id.trim()) {
|
|
17
|
-
return req.id
|
|
18
|
-
}
|
|
19
16
|
const parent = this.currentParent(req)
|
|
20
17
|
if (parent && typeof parent.id === "string" && parent.id.trim()) {
|
|
21
18
|
return parent.id
|
|
22
19
|
}
|
|
20
|
+
if (req && typeof req.id === "string" && req.id.trim()) {
|
|
21
|
+
return req.id
|
|
22
|
+
}
|
|
23
23
|
return undefined
|
|
24
24
|
}
|
|
25
25
|
currentCaller(req) {
|
|
@@ -192,14 +192,16 @@ class Script {
|
|
|
192
192
|
input: req.params.params,
|
|
193
193
|
client: this.currentClient(req),
|
|
194
194
|
}
|
|
195
|
-
if (req.id) {
|
|
196
|
-
request.id = req.id
|
|
197
|
-
}
|
|
198
195
|
const caller = this.currentCaller(req)
|
|
199
196
|
if (caller) {
|
|
200
197
|
request.caller = caller
|
|
201
|
-
} else
|
|
202
|
-
|
|
198
|
+
} else {
|
|
199
|
+
const parent = this.currentParent(req)
|
|
200
|
+
if (parent && typeof parent.id === "string" && parent.id.trim()) {
|
|
201
|
+
request.caller = parent.id
|
|
202
|
+
} else if (req.parent && req.parent.path) {
|
|
203
|
+
request.caller = req.parent.path
|
|
204
|
+
}
|
|
203
205
|
}
|
|
204
206
|
const origin = this.currentOrigin(req)
|
|
205
207
|
if (origin) {
|
|
@@ -2,15 +2,35 @@ const path = require('path')
|
|
|
2
2
|
const fs = require('fs')
|
|
3
3
|
const os = require('os')
|
|
4
4
|
class Shell {
|
|
5
|
-
applyBluefairyDefault(req = {}) {
|
|
6
|
-
if (!req.params
|
|
5
|
+
async applyBluefairyDefault(req = {}, kernel) {
|
|
6
|
+
if (!req.params) {
|
|
7
|
+
return
|
|
8
|
+
}
|
|
9
|
+
if (Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
|
|
10
|
+
return
|
|
11
|
+
}
|
|
12
|
+
const preferences = kernel && kernel.appPreferences
|
|
13
|
+
if (
|
|
14
|
+
!preferences
|
|
15
|
+
|| !kernel
|
|
16
|
+
|| !kernel.api
|
|
17
|
+
|| typeof kernel.api.resolvePath !== "function"
|
|
18
|
+
|| typeof preferences.resolveAppIdFromPath !== "function"
|
|
19
|
+
|| typeof preferences.getPreference !== "function"
|
|
20
|
+
) {
|
|
21
|
+
req.params.bluefairy = "off"
|
|
7
22
|
return
|
|
8
23
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
24
|
+
const cwd = req.cwd || kernel.homedir
|
|
25
|
+
const requestedPath = req.params.path || "."
|
|
26
|
+
const resolvedPath = kernel.api.resolvePath(cwd, requestedPath)
|
|
27
|
+
const appId = preferences.resolveAppIdFromPath(resolvedPath)
|
|
28
|
+
if (!appId) {
|
|
12
29
|
req.params.bluefairy = "off"
|
|
30
|
+
return
|
|
13
31
|
}
|
|
32
|
+
const preference = await preferences.getPreference(appId)
|
|
33
|
+
req.params.bluefairy = preference && preference.protection_enabled === true ? "on" : "off"
|
|
14
34
|
}
|
|
15
35
|
async start(req, ondata, kernel) {
|
|
16
36
|
/*
|
|
@@ -50,7 +70,9 @@ class Shell {
|
|
|
50
70
|
if (req.params) {
|
|
51
71
|
req.params.$parent = req.parent
|
|
52
72
|
}
|
|
53
|
-
|
|
73
|
+
if (!Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
|
|
74
|
+
req.params.bluefairy = "off"
|
|
75
|
+
}
|
|
54
76
|
|
|
55
77
|
// // create a persistent session
|
|
56
78
|
// req.params.persistent = true
|
|
@@ -136,10 +158,13 @@ class Shell {
|
|
|
136
158
|
if (!req.params) {
|
|
137
159
|
req.params = {}
|
|
138
160
|
}
|
|
161
|
+
if (!req.params.path) {
|
|
162
|
+
req.params.path = req.cwd
|
|
163
|
+
}
|
|
139
164
|
if (req.params) {
|
|
140
165
|
req.params.$parent = req.parent
|
|
141
166
|
}
|
|
142
|
-
this.applyBluefairyDefault(req)
|
|
167
|
+
await this.applyBluefairyDefault(req, kernel)
|
|
143
168
|
let options = {}
|
|
144
169
|
if (req.cwd) options.cwd = req.cwd
|
|
145
170
|
if (req.parent && req.parent.id) {
|
package/kernel/bin/bluefairy.js
CHANGED
|
@@ -4,7 +4,7 @@ const semver = require('semver')
|
|
|
4
4
|
|
|
5
5
|
class Bluefairy {
|
|
6
6
|
description = "Installs Bluefairy, a standalone package freshness guard."
|
|
7
|
-
version = ">=0.0.
|
|
7
|
+
version = ">=0.0.31"
|
|
8
8
|
|
|
9
9
|
packageName() {
|
|
10
10
|
return "bluefairy"
|
|
@@ -37,6 +37,10 @@ class Bluefairy {
|
|
|
37
37
|
return this.kernel.bin.path("bluefairy")
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
packageInstallPath() {
|
|
41
|
+
return path.resolve(this.moduleRoot(), this.packageName())
|
|
42
|
+
}
|
|
43
|
+
|
|
40
44
|
env() {
|
|
41
45
|
return {
|
|
42
46
|
BLUEFAIRY_HOME: this.runtimeHome()
|
|
@@ -51,11 +55,29 @@ class Bluefairy {
|
|
|
51
55
|
path.resolve(binDir, "bluefairy-activate"),
|
|
52
56
|
path.resolve(binDir, "bluefairy-activate.cmd"),
|
|
53
57
|
path.resolve(binDir, "bluefairy-activate.ps1"),
|
|
58
|
+
path.resolve(binDir, "bluefairy-deactivate"),
|
|
59
|
+
path.resolve(binDir, "bluefairy-deactivate.cmd"),
|
|
60
|
+
path.resolve(binDir, "bluefairy-deactivate.ps1"),
|
|
54
61
|
]
|
|
55
62
|
}
|
|
56
63
|
return [
|
|
57
64
|
path.resolve(binDir, "bluefairy"),
|
|
58
65
|
path.resolve(binDir, "bluefairy-activate"),
|
|
66
|
+
path.resolve(binDir, "bluefairy-deactivate"),
|
|
67
|
+
]
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
binLauncherArtifacts() {
|
|
71
|
+
const binDir = this.npmBinDir()
|
|
72
|
+
if (this.kernel.platform === "win32") {
|
|
73
|
+
return [
|
|
74
|
+
path.resolve(binDir, "bluefairy"),
|
|
75
|
+
path.resolve(binDir, "bluefairy.cmd"),
|
|
76
|
+
path.resolve(binDir, "bluefairy.ps1"),
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
return [
|
|
80
|
+
path.resolve(binDir, "bluefairy"),
|
|
59
81
|
]
|
|
60
82
|
}
|
|
61
83
|
|
|
@@ -93,18 +115,61 @@ class Bluefairy {
|
|
|
93
115
|
}
|
|
94
116
|
}
|
|
95
117
|
|
|
96
|
-
async
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
118
|
+
async removePath(target, ondata, label) {
|
|
119
|
+
if (!fs.existsSync(target)) {
|
|
120
|
+
return
|
|
121
|
+
}
|
|
122
|
+
try {
|
|
123
|
+
await fs.promises.rm(target, { recursive: true, force: true })
|
|
124
|
+
ondata({ raw: `Removed ${label}: ${target}\r\n` })
|
|
125
|
+
} catch (error) {
|
|
126
|
+
const message = error && error.message ? error.message : String(error)
|
|
127
|
+
ondata({ raw: `Warning: failed to remove ${label} ${target}: ${message}\r\n` })
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async removeRetiredEntries(parentDir, ondata) {
|
|
132
|
+
let entries
|
|
133
|
+
try {
|
|
134
|
+
entries = await fs.promises.readdir(parentDir)
|
|
135
|
+
} catch (error) {
|
|
136
|
+
const message = error && error.message ? error.message : String(error)
|
|
137
|
+
ondata({ raw: `Warning: failed to list ${parentDir}: ${message}\r\n` })
|
|
138
|
+
return
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const retiredPrefix = `.${this.packageName()}-`
|
|
142
|
+
for (const entry of entries) {
|
|
143
|
+
if (!entry.startsWith(retiredPrefix)) {
|
|
144
|
+
continue
|
|
106
145
|
}
|
|
146
|
+
await this.removePath(
|
|
147
|
+
path.resolve(parentDir, entry),
|
|
148
|
+
ondata,
|
|
149
|
+
"stale Bluefairy install temp"
|
|
150
|
+
)
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async cleanupInstallState(ondata) {
|
|
155
|
+
const cleanupTargets = new Set([
|
|
156
|
+
this.runtimeHome(),
|
|
157
|
+
this.packageInstallPath(),
|
|
158
|
+
...this.installedArtifacts(),
|
|
159
|
+
...this.binLauncherArtifacts(),
|
|
160
|
+
])
|
|
161
|
+
|
|
162
|
+
for (const target of cleanupTargets) {
|
|
163
|
+
await this.removePath(target, ondata, "existing Bluefairy install state")
|
|
107
164
|
}
|
|
165
|
+
|
|
166
|
+
await this.removeRetiredEntries(this.moduleRoot(), ondata)
|
|
167
|
+
await this.removeRetiredEntries(this.npmBinDir(), ondata)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async install(req, ondata) {
|
|
171
|
+
const spec = this.packageSpec().replaceAll('"', '\\"')
|
|
172
|
+
await this.cleanupInstallState(ondata)
|
|
108
173
|
await this.kernel.exec({
|
|
109
174
|
env: this.env(),
|
|
110
175
|
message: `npm install -g "${spec}" --force`,
|
package/kernel/shell.js
CHANGED
|
@@ -21,6 +21,51 @@ const AnsiStreamTracker = require('./ansi_stream_tracker')
|
|
|
21
21
|
const ShellStateSync = require('./shell_state_sync')
|
|
22
22
|
const home = os.homedir()
|
|
23
23
|
|
|
24
|
+
function normalizeComparablePath(filePath, platform) {
|
|
25
|
+
const normalized = path.normalize(filePath)
|
|
26
|
+
return platform === 'win32' ? normalized.toLowerCase() : normalized
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function isBluefairyShimPath(filePath, platform) {
|
|
30
|
+
if (!filePath || typeof filePath !== "string") {
|
|
31
|
+
return false
|
|
32
|
+
}
|
|
33
|
+
const normalized = normalizeComparablePath(filePath.trim(), platform)
|
|
34
|
+
if (!normalized) {
|
|
35
|
+
return false
|
|
36
|
+
}
|
|
37
|
+
const shimDirName = path.basename(normalized)
|
|
38
|
+
if (shimDirName !== "shims") {
|
|
39
|
+
return false
|
|
40
|
+
}
|
|
41
|
+
const parentDirName = path.basename(path.dirname(normalized))
|
|
42
|
+
return parentDirName === "bluefairy" || parentDirName === ".bluefairy"
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function stripBluefairyShimPaths(pathValue, platform) {
|
|
46
|
+
if (!pathValue || typeof pathValue !== "string") {
|
|
47
|
+
return pathValue
|
|
48
|
+
}
|
|
49
|
+
return pathValue
|
|
50
|
+
.split(path.delimiter)
|
|
51
|
+
.filter((entry) => entry && !isBluefairyShimPath(entry, platform))
|
|
52
|
+
.join(path.delimiter)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const CONDA_ACTIVATION_STATE_PATTERN = /^(?:_CE_(?:M|CONDA)|CONDA_(?:EXE|PYTHON_EXE|PREFIX(?:_\d+)?|DEFAULT_ENV|PROMPT_MODIFIER|SHLVL|PS1_BACKUP))$/
|
|
56
|
+
|
|
57
|
+
function isCondaActivationStateKey(key) {
|
|
58
|
+
return typeof key === "string" && CONDA_ACTIVATION_STATE_PATTERN.test(key)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stripInheritedCondaActivationState(env) {
|
|
62
|
+
for (const key of Object.keys(env)) {
|
|
63
|
+
if (isCondaActivationStateKey(key)) {
|
|
64
|
+
delete env[key]
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
24
69
|
// xterm.js currently ignores DECSYNCTERM (CSI ? 2026 h/l) and renders it as text on Windows.
|
|
25
70
|
// filterDecsync() removes these sequences so they do not pollute the terminal output.
|
|
26
71
|
class Shell {
|
|
@@ -91,6 +136,12 @@ class Shell {
|
|
|
91
136
|
}
|
|
92
137
|
async init_env(params) {
|
|
93
138
|
this.env = Object.assign({}, process.env)
|
|
139
|
+
for (const key of Object.keys(this.env)) {
|
|
140
|
+
if (key.toUpperCase().startsWith("BLUEFAIRY_")) {
|
|
141
|
+
delete this.env[key]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
stripInheritedCondaActivationState(this.env)
|
|
94
145
|
// If the user has set PYTHONPATH, unset it.
|
|
95
146
|
if (this.env.PYTHONPATH) {
|
|
96
147
|
delete this.env.PYTHONPATH
|
|
@@ -166,6 +217,7 @@ class Shell {
|
|
|
166
217
|
let app_env = await Environment.get(api_path, this.kernel)
|
|
167
218
|
this.env = Object.assign(this.env, app_env)
|
|
168
219
|
}
|
|
220
|
+
stripInheritedCondaActivationState(this.env)
|
|
169
221
|
let PATH_KEY = Object.keys(this.env).find((key) => key.toLowerCase() === "path") || "PATH";
|
|
170
222
|
if (!this.env[PATH_KEY]) {
|
|
171
223
|
// fall back to whichever casing exists so we don't end up writing to an undefined key
|
|
@@ -232,6 +284,9 @@ class Shell {
|
|
|
232
284
|
}
|
|
233
285
|
}
|
|
234
286
|
|
|
287
|
+
stripInheritedCondaActivationState(this.env)
|
|
288
|
+
this.env[PATH_KEY] = stripBluefairyShimPaths(this.env[PATH_KEY], this.platform)
|
|
289
|
+
|
|
235
290
|
for(let key in this.env) {
|
|
236
291
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(key) && key !== "ProgramFiles(x86)") {
|
|
237
292
|
delete this.env[key]
|
package/package.json
CHANGED
package/server/index.js
CHANGED
|
@@ -213,6 +213,7 @@ class Server {
|
|
|
213
213
|
})
|
|
214
214
|
this.appRegistry = new AppRegistryService({ kernel: this.kernel })
|
|
215
215
|
this.appPreferences = new AppPreferencesService({ kernel: this.kernel })
|
|
216
|
+
this.kernel.appPreferences = this.appPreferences
|
|
216
217
|
this.appLogs = new AppLogService({ registry: this.appRegistry })
|
|
217
218
|
this.appSearch = new AppSearchService({
|
|
218
219
|
kernel: this.kernel,
|
|
@@ -1251,7 +1252,7 @@ class Server {
|
|
|
1251
1252
|
path: 'remote.origin.url'
|
|
1252
1253
|
})
|
|
1253
1254
|
if (gitRemote && this.portal) {
|
|
1254
|
-
community_url = `${this.portal}/resolve?url=${encodeURIComponent(gitRemote)}&embed=1&theme=${encodeURIComponent(this.theme)}`
|
|
1255
|
+
community_url = `${this.portal}/resolve?url=${encodeURIComponent(gitRemote)}&embed=1&theme=${encodeURIComponent(this.theme)}&pinokio_checkin_bridge=v1`
|
|
1255
1256
|
}
|
|
1256
1257
|
} catch (_) {
|
|
1257
1258
|
community_url = ""
|
|
@@ -6417,8 +6418,6 @@ class Server {
|
|
|
6417
6418
|
} else {
|
|
6418
6419
|
registryOverride = null
|
|
6419
6420
|
}
|
|
6420
|
-
console.log("[registry/checkin] repo=%s return=%s registryRaw=%s registryOverride=%s", repoUrl || "", returnUrl || "", registryRaw || "", registryOverride || "")
|
|
6421
|
-
|
|
6422
6421
|
let candidates = []
|
|
6423
6422
|
if (repoUrl && this.kernel && this.kernel.git) {
|
|
6424
6423
|
const apiRoot = this.kernel.path('api')
|
|
@@ -6538,7 +6537,6 @@ class Server {
|
|
|
6538
6537
|
if (wantPublish) {
|
|
6539
6538
|
const registry = await this.getRegistryConfig()
|
|
6540
6539
|
const baseUrl = registryOverride || (registry && registry.url ? String(registry.url).replace(/\/$/, '') : null)
|
|
6541
|
-
console.log("[checkpoints/snapshot] publish=1 registryOverride=%s baseUrl=%s hasToken=%s", registryOverride || "", baseUrl || "", registryToken ? "yes" : "no")
|
|
6542
6540
|
if (!baseUrl || !registryToken) {
|
|
6543
6541
|
await this.kernel.git.setCheckpointSync(created.remoteKey, created.id, { status: "needs_token", at: Date.now() }).catch(() => {})
|
|
6544
6542
|
res.json({ ok: true, created: created || null, publish: { ok: false, code: "missing_token" } })
|
|
@@ -8268,6 +8266,67 @@ class Server {
|
|
|
8268
8266
|
Math.random().toString(16).slice(2)
|
|
8269
8267
|
].join("-")
|
|
8270
8268
|
}
|
|
8269
|
+
const prepareUniversalLauncherTarget = async ({ type, name, prompt, tool, uploadToken }) => {
|
|
8270
|
+
const normalizedType = typeof type === "string" ? type.trim().toLowerCase() : ""
|
|
8271
|
+
if (normalizedType !== "ask" && normalizedType !== "create_plugin") {
|
|
8272
|
+
const error = new Error("Unsupported launcher type.")
|
|
8273
|
+
error.status = 400
|
|
8274
|
+
throw error
|
|
8275
|
+
}
|
|
8276
|
+
|
|
8277
|
+
const pluginHref = resolveUniversalLauncherPluginHref(tool)
|
|
8278
|
+
let folderName = typeof name === "string" ? name.trim() : ""
|
|
8279
|
+
if (normalizedType === "ask" && !folderName) {
|
|
8280
|
+
folderName = await generateTerminalWorkspaceFolderName()
|
|
8281
|
+
}
|
|
8282
|
+
if (!folderName) {
|
|
8283
|
+
const error = new Error("Folder name is required.")
|
|
8284
|
+
error.status = 400
|
|
8285
|
+
throw error
|
|
8286
|
+
}
|
|
8287
|
+
|
|
8288
|
+
const rootDir = normalizedType === "ask"
|
|
8289
|
+
? path.resolve(getTerminalWorkspacesRoot())
|
|
8290
|
+
: path.resolve(this.kernel.path("plugin"))
|
|
8291
|
+
const targetPath = await createLauncherTargetFolder(rootDir, folderName)
|
|
8292
|
+
try {
|
|
8293
|
+
await copyLauncherUploadsToDir(uploadToken, targetPath)
|
|
8294
|
+
} catch (error) {
|
|
8295
|
+
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
8296
|
+
throw error
|
|
8297
|
+
}
|
|
8298
|
+
try {
|
|
8299
|
+
await bootstrapLauncherInstructionFiles(targetPath)
|
|
8300
|
+
} catch (error) {
|
|
8301
|
+
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
8302
|
+
throw error
|
|
8303
|
+
}
|
|
8304
|
+
try {
|
|
8305
|
+
await persistLauncherPromptContext(targetPath, {
|
|
8306
|
+
prompt,
|
|
8307
|
+
includeSpec: true,
|
|
8308
|
+
includeRequest: normalizedType === "ask"
|
|
8309
|
+
})
|
|
8310
|
+
} catch (error) {
|
|
8311
|
+
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
8312
|
+
throw error
|
|
8313
|
+
}
|
|
8314
|
+
|
|
8315
|
+
const params = new URLSearchParams()
|
|
8316
|
+
params.set("cwd", targetPath)
|
|
8317
|
+
params.set("chrome", "full")
|
|
8318
|
+
if (prompt) {
|
|
8319
|
+
params.set("prompt", prompt)
|
|
8320
|
+
}
|
|
8321
|
+
|
|
8322
|
+
return {
|
|
8323
|
+
ok: true,
|
|
8324
|
+
type: normalizedType,
|
|
8325
|
+
name: folderName,
|
|
8326
|
+
cwd: targetPath,
|
|
8327
|
+
url: `${pluginHref}?${params.toString()}`
|
|
8328
|
+
}
|
|
8329
|
+
}
|
|
8271
8330
|
const prepareUniversalCreateApp = async ({ name, prompt, tool, uploadToken }) => {
|
|
8272
8331
|
const folderName = typeof name === "string" ? name.trim() : ""
|
|
8273
8332
|
if (!folderName) {
|
|
@@ -8345,6 +8404,44 @@ class Server {
|
|
|
8345
8404
|
}
|
|
8346
8405
|
return `${baseName}-${Date.now()}`
|
|
8347
8406
|
}
|
|
8407
|
+
const suggestTaskWorkspaceName = async (task) => {
|
|
8408
|
+
const baseName = taskPackages.slugify(
|
|
8409
|
+
task && task.config ? task.config.title : (task && task.id ? task.id : "task"),
|
|
8410
|
+
"task"
|
|
8411
|
+
)
|
|
8412
|
+
let links = { workspaces: [] }
|
|
8413
|
+
try {
|
|
8414
|
+
links = await taskWorkspaceLinks.listTaskWorkspaces(task && task.id ? task.id : "", {
|
|
8415
|
+
root: "workspaces",
|
|
8416
|
+
pruneMissing: true
|
|
8417
|
+
})
|
|
8418
|
+
} catch (_) {
|
|
8419
|
+
}
|
|
8420
|
+
const existingNames = new Set(
|
|
8421
|
+
(Array.isArray(links.workspaces) ? links.workspaces : [])
|
|
8422
|
+
.map((workspace) => {
|
|
8423
|
+
const ref = workspace && typeof workspace.ref === "string" ? workspace.ref.trim() : ""
|
|
8424
|
+
if (!ref) {
|
|
8425
|
+
return ""
|
|
8426
|
+
}
|
|
8427
|
+
const relative = ref.split("/").slice(1).join("/")
|
|
8428
|
+
return relative ? path.posix.basename(relative).toLowerCase() : ""
|
|
8429
|
+
})
|
|
8430
|
+
.filter(Boolean)
|
|
8431
|
+
)
|
|
8432
|
+
let attempt = 0
|
|
8433
|
+
while (attempt < 1000) {
|
|
8434
|
+
const suffix = attempt === 0 ? "" : `-${attempt + 1}`
|
|
8435
|
+
const maxBaseLength = Math.max(1, 80 - suffix.length)
|
|
8436
|
+
const candidateBase = baseName.slice(0, maxBaseLength) || "task"
|
|
8437
|
+
const candidate = `${candidateBase}${suffix}`
|
|
8438
|
+
if (!existingNames.has(candidate.toLowerCase())) {
|
|
8439
|
+
return candidate
|
|
8440
|
+
}
|
|
8441
|
+
attempt += 1
|
|
8442
|
+
}
|
|
8443
|
+
return `${baseName}-${Date.now()}`
|
|
8444
|
+
}
|
|
8348
8445
|
const getTaskLaunchTarget = (taskConfig) => {
|
|
8349
8446
|
const validatedTaskConfig = taskPackages.validateTaskConfig(taskConfig)
|
|
8350
8447
|
return validatedTaskConfig.target
|
|
@@ -8656,7 +8753,7 @@ class Server {
|
|
|
8656
8753
|
const taskUi = buildTaskPresentationState(task, shareState)
|
|
8657
8754
|
const sidebarContext = await buildTaskSidebarContext()
|
|
8658
8755
|
const suggestedFolderName = task && task.config && usesWorkspaceTaskTarget(task.config)
|
|
8659
|
-
?
|
|
8756
|
+
? await suggestTaskWorkspaceName(task)
|
|
8660
8757
|
: taskPackages.slugify(task && task.config ? task.config.title : task.id, task && task.id ? task.id : "task")
|
|
8661
8758
|
const renderedPrompt = taskPackages.applyTemplateValues(task.template, promptValues)
|
|
8662
8759
|
const protocol = (req.$source && req.$source.protocol) || req.protocol || "http"
|
|
@@ -9943,37 +10040,84 @@ class Server {
|
|
|
9943
10040
|
}
|
|
9944
10041
|
|
|
9945
10042
|
const pluginHref = resolveUniversalLauncherPluginHref(selectedTool)
|
|
10043
|
+
const launchTarget = getTaskLaunchTarget(task.config)
|
|
9946
10044
|
const launchRoot = getTaskLaunchRoot(task.config)
|
|
9947
10045
|
let folderName = folderNameInput
|
|
9948
10046
|
if (!folderName) {
|
|
9949
|
-
if (
|
|
9950
|
-
folderName = await
|
|
10047
|
+
if (launchTarget === "workspaces") {
|
|
10048
|
+
folderName = await suggestTaskWorkspaceName(task)
|
|
9951
10049
|
} else {
|
|
9952
|
-
|
|
10050
|
+
await renderTaskLaunchPage(req, res, task, {
|
|
10051
|
+
selectedTool,
|
|
10052
|
+
inputValues,
|
|
10053
|
+
folderName: "",
|
|
10054
|
+
error: "Folder name is required."
|
|
10055
|
+
})
|
|
10056
|
+
return
|
|
9953
10057
|
}
|
|
9954
10058
|
}
|
|
9955
10059
|
|
|
9956
|
-
|
|
9957
|
-
|
|
9958
|
-
targetPath = await createLauncherTargetFolder(launchRoot, folderName)
|
|
9959
|
-
} catch (error) {
|
|
10060
|
+
const prompt = taskPackages.applyTemplateValues(task.template, filterFilledTaskInputValues(inputValues)).trim()
|
|
10061
|
+
if (!prompt) {
|
|
9960
10062
|
await renderTaskLaunchPage(req, res, task, {
|
|
9961
10063
|
selectedTool,
|
|
9962
10064
|
inputValues,
|
|
9963
10065
|
folderName: folderNameInput || folderName,
|
|
9964
|
-
error:
|
|
10066
|
+
error: "The rendered task prompt is empty."
|
|
9965
10067
|
})
|
|
9966
10068
|
return
|
|
9967
10069
|
}
|
|
9968
10070
|
|
|
9969
|
-
|
|
9970
|
-
|
|
9971
|
-
|
|
10071
|
+
if (launchTarget === "api") {
|
|
10072
|
+
try {
|
|
10073
|
+
const payload = await prepareUniversalCreateApp({
|
|
10074
|
+
name: folderName,
|
|
10075
|
+
prompt,
|
|
10076
|
+
tool: selectedTool,
|
|
10077
|
+
uploadToken: ""
|
|
10078
|
+
})
|
|
10079
|
+
res.redirect(payload.url)
|
|
10080
|
+
} catch (error) {
|
|
10081
|
+
await renderTaskLaunchPage(req, res, task, {
|
|
10082
|
+
selectedTool,
|
|
10083
|
+
inputValues,
|
|
10084
|
+
folderName: folderNameInput || folderName,
|
|
10085
|
+
error: error && error.message ? error.message : "Failed to create app."
|
|
10086
|
+
})
|
|
10087
|
+
}
|
|
10088
|
+
return
|
|
10089
|
+
}
|
|
10090
|
+
|
|
10091
|
+
if (launchTarget === "plugin") {
|
|
10092
|
+
try {
|
|
10093
|
+
const payload = await prepareUniversalLauncherTarget({
|
|
10094
|
+
type: "create_plugin",
|
|
10095
|
+
name: folderName,
|
|
10096
|
+
prompt,
|
|
10097
|
+
tool: selectedTool,
|
|
10098
|
+
uploadToken: ""
|
|
10099
|
+
})
|
|
10100
|
+
res.redirect(payload.url)
|
|
10101
|
+
} catch (error) {
|
|
10102
|
+
await renderTaskLaunchPage(req, res, task, {
|
|
10103
|
+
selectedTool,
|
|
10104
|
+
inputValues,
|
|
10105
|
+
folderName: folderNameInput || folderName,
|
|
10106
|
+
error: error && error.message ? error.message : "Failed to initialize task folder."
|
|
10107
|
+
})
|
|
10108
|
+
}
|
|
10109
|
+
return
|
|
10110
|
+
}
|
|
10111
|
+
|
|
10112
|
+
let targetPath
|
|
10113
|
+
try {
|
|
10114
|
+
targetPath = await createLauncherTargetFolder(launchRoot, folderName)
|
|
10115
|
+
} catch (error) {
|
|
9972
10116
|
await renderTaskLaunchPage(req, res, task, {
|
|
9973
10117
|
selectedTool,
|
|
9974
10118
|
inputValues,
|
|
9975
10119
|
folderName: folderNameInput || folderName,
|
|
9976
|
-
error: "
|
|
10120
|
+
error: error && error.message ? error.message : "Failed to create task folder."
|
|
9977
10121
|
})
|
|
9978
10122
|
return
|
|
9979
10123
|
}
|
|
@@ -10006,6 +10150,29 @@ class Server {
|
|
|
10006
10150
|
})
|
|
10007
10151
|
return
|
|
10008
10152
|
}
|
|
10153
|
+
if (launchTarget === "workspaces") {
|
|
10154
|
+
const workspaceRef = taskWorkspaceLinks.createWorkspaceRef("workspaces", targetPath)
|
|
10155
|
+
if (!workspaceRef) {
|
|
10156
|
+
await renderTaskLaunchPage(req, res, task, {
|
|
10157
|
+
selectedTool,
|
|
10158
|
+
inputValues,
|
|
10159
|
+
folderName: folderNameInput || folderName,
|
|
10160
|
+
error: "Failed to remember the created workspace."
|
|
10161
|
+
})
|
|
10162
|
+
return
|
|
10163
|
+
}
|
|
10164
|
+
try {
|
|
10165
|
+
await taskWorkspaceLinks.touchTaskWorkspace(task.id, workspaceRef)
|
|
10166
|
+
} catch (error) {
|
|
10167
|
+
await renderTaskLaunchPage(req, res, task, {
|
|
10168
|
+
selectedTool,
|
|
10169
|
+
inputValues,
|
|
10170
|
+
folderName: folderNameInput || folderName,
|
|
10171
|
+
error: error && error.message ? error.message : "Failed to remember the created workspace."
|
|
10172
|
+
})
|
|
10173
|
+
return
|
|
10174
|
+
}
|
|
10175
|
+
}
|
|
10009
10176
|
|
|
10010
10177
|
const params = new URLSearchParams()
|
|
10011
10178
|
params.set("cwd", targetPath)
|
|
@@ -10438,9 +10605,7 @@ class Server {
|
|
|
10438
10605
|
try {
|
|
10439
10606
|
const body = req.body && typeof req.body === "object" ? req.body : {}
|
|
10440
10607
|
const type = typeof body.type === "string" ? body.type.trim().toLowerCase() : ""
|
|
10441
|
-
const requestedFolderName = typeof body.name === "string" ? body.name.trim() : ""
|
|
10442
10608
|
const prompt = typeof body.prompt === "string" ? body.prompt.trim() : ""
|
|
10443
|
-
const pluginHref = resolveUniversalLauncherPluginHref(body.tool)
|
|
10444
10609
|
|
|
10445
10610
|
if (type !== "ask" && type !== "create_plugin") {
|
|
10446
10611
|
res.status(400).json({
|
|
@@ -10449,59 +10614,14 @@ class Server {
|
|
|
10449
10614
|
})
|
|
10450
10615
|
return
|
|
10451
10616
|
}
|
|
10452
|
-
|
|
10453
|
-
if (type === "ask" && !folderName) {
|
|
10454
|
-
folderName = await generateTerminalWorkspaceFolderName()
|
|
10455
|
-
}
|
|
10456
|
-
if (!folderName) {
|
|
10457
|
-
res.status(400).json({
|
|
10458
|
-
ok: false,
|
|
10459
|
-
error: "Folder name is required."
|
|
10460
|
-
})
|
|
10461
|
-
return
|
|
10462
|
-
}
|
|
10463
|
-
|
|
10464
|
-
const rootDir = type === "ask"
|
|
10465
|
-
? path.resolve(getTerminalWorkspacesRoot())
|
|
10466
|
-
: path.resolve(this.kernel.path("plugin"))
|
|
10467
|
-
const targetPath = await createLauncherTargetFolder(rootDir, folderName)
|
|
10468
|
-
try {
|
|
10469
|
-
await copyLauncherUploadsToDir(body.uploadToken, targetPath)
|
|
10470
|
-
} catch (error) {
|
|
10471
|
-
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
10472
|
-
throw error
|
|
10473
|
-
}
|
|
10474
|
-
try {
|
|
10475
|
-
await bootstrapLauncherInstructionFiles(targetPath)
|
|
10476
|
-
} catch (error) {
|
|
10477
|
-
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
10478
|
-
throw error
|
|
10479
|
-
}
|
|
10480
|
-
try {
|
|
10481
|
-
await persistLauncherPromptContext(targetPath, {
|
|
10482
|
-
prompt,
|
|
10483
|
-
includeSpec: true,
|
|
10484
|
-
includeRequest: type === "ask"
|
|
10485
|
-
})
|
|
10486
|
-
} catch (error) {
|
|
10487
|
-
await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
|
|
10488
|
-
throw error
|
|
10489
|
-
}
|
|
10490
|
-
|
|
10491
|
-
const params = new URLSearchParams()
|
|
10492
|
-
params.set("cwd", targetPath)
|
|
10493
|
-
params.set("chrome", "full")
|
|
10494
|
-
if (prompt) {
|
|
10495
|
-
params.set("prompt", prompt)
|
|
10496
|
-
}
|
|
10497
|
-
|
|
10498
|
-
res.json({
|
|
10499
|
-
ok: true,
|
|
10617
|
+
const payload = await prepareUniversalLauncherTarget({
|
|
10500
10618
|
type,
|
|
10501
|
-
name:
|
|
10502
|
-
|
|
10503
|
-
|
|
10619
|
+
name: typeof body.name === "string" ? body.name : "",
|
|
10620
|
+
prompt,
|
|
10621
|
+
tool: typeof body.tool === "string" ? body.tool : "",
|
|
10622
|
+
uploadToken: typeof body.uploadToken === "string" ? body.uploadToken : ""
|
|
10504
10623
|
})
|
|
10624
|
+
res.json(payload)
|
|
10505
10625
|
} catch (error) {
|
|
10506
10626
|
const status = Number.isInteger(error && error.status) ? error.status : 500
|
|
10507
10627
|
res.status(status).json({
|
|
@@ -12592,12 +12712,14 @@ class Server {
|
|
|
12592
12712
|
// }
|
|
12593
12713
|
//
|
|
12594
12714
|
res.render("setup", {
|
|
12715
|
+
mode: req.params.mode,
|
|
12595
12716
|
wait,
|
|
12596
12717
|
error,
|
|
12597
12718
|
current,
|
|
12598
12719
|
install_required,
|
|
12599
12720
|
requirements,
|
|
12600
12721
|
requirements_pending,
|
|
12722
|
+
portal: this.portal,
|
|
12601
12723
|
logo: this.logo,
|
|
12602
12724
|
theme: this.theme,
|
|
12603
12725
|
agent: req.agent,
|