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.
@@ -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 if (req.parent && req.parent.path) {
202
- request.caller = req.parent.path
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 || Object.prototype.hasOwnProperty.call(req.params, "bluefairy")) {
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
- if (req.parent && Object.prototype.hasOwnProperty.call(req.parent, "protection_enabled")) {
10
- req.params.bluefairy = req.parent.protection_enabled === false ? "off" : "on"
11
- } else {
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
- this.applyBluefairyDefault(req)
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) {
@@ -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.27"
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 install(req, ondata) {
97
- const spec = this.packageSpec().replaceAll('"', '\\"')
98
- const runtimeHome = this.runtimeHome()
99
- if (fs.existsSync(runtimeHome)) {
100
- try {
101
- await fs.promises.rm(runtimeHome, { recursive: true, force: true })
102
- ondata({ raw: `Removed existing Bluefairy runtime: ${runtimeHome}\r\n` })
103
- } catch (error) {
104
- const message = error && error.message ? error.message : String(error)
105
- ondata({ raw: `Warning: failed to remove Bluefairy runtime ${runtimeHome}: ${message}\r\n` })
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "7.1.72",
3
+ "version": "7.1.74",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
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 (usesWorkspaceTaskTarget(task.config)) {
9950
- folderName = await generateTerminalWorkspaceFolderName()
10047
+ if (launchTarget === "workspaces") {
10048
+ folderName = await suggestTaskWorkspaceName(task)
9951
10049
  } else {
9952
- folderName = await suggestTaskFolderName(launchRoot, task.config.title)
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
- let targetPath
9957
- try {
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: error && error.message ? error.message : "Failed to create task folder."
10066
+ error: "The rendered task prompt is empty."
9965
10067
  })
9966
10068
  return
9967
10069
  }
9968
10070
 
9969
- const prompt = taskPackages.applyTemplateValues(task.template, filterFilledTaskInputValues(inputValues)).trim()
9970
- if (!prompt) {
9971
- await fs.promises.rm(targetPath, { recursive: true, force: true }).catch(() => {})
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: "The rendered task prompt is empty."
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
- let folderName = requestedFolderName
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: folderName,
10502
- cwd: targetPath,
10503
- url: `${pluginHref}?${params.toString()}`
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,