pinokiod 6.0.86 → 6.0.89

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.
@@ -28,221 +28,6 @@ class Api {
28
28
  this.child_procs = {}
29
29
  this.lproxy = new Lproxy()
30
30
  }
31
- parseQueryString(query) {
32
- const result = {}
33
- if (typeof query !== "string" || query.length === 0) {
34
- return result
35
- }
36
- const segments = query.split("&")
37
- for (const segment of segments) {
38
- if (!segment) {
39
- continue
40
- }
41
- const equalsIndex = segment.indexOf("=")
42
- const rawKey = equalsIndex >= 0 ? segment.slice(0, equalsIndex) : segment
43
- if (!rawKey) {
44
- continue
45
- }
46
- const rawValue = equalsIndex >= 0 ? segment.slice(equalsIndex + 1) : ""
47
- const plusDecodedKey = rawKey.replace(/\+/g, " ")
48
- const plusDecodedValue = rawValue.replace(/\+/g, " ")
49
- let key
50
- let value
51
- try {
52
- key = decodeURIComponent(plusDecodedKey)
53
- } catch (_) {
54
- key = plusDecodedKey
55
- }
56
- try {
57
- value = decodeURIComponent(plusDecodedValue)
58
- } catch (_) {
59
- value = plusDecodedValue
60
- }
61
- if (Object.prototype.hasOwnProperty.call(result, key)) {
62
- if (Array.isArray(result[key])) {
63
- result[key].push(value)
64
- } else {
65
- result[key] = [result[key], value]
66
- }
67
- } else {
68
- result[key] = value
69
- }
70
- }
71
- return result
72
- }
73
- stringifyQueryParams(params) {
74
- const searchParams = new URLSearchParams()
75
- if (!params || typeof params !== "object") {
76
- return searchParams.toString()
77
- }
78
- for (const [key, rawValue] of Object.entries(params)) {
79
- if (typeof key !== "string" || key.length === 0) {
80
- continue
81
- }
82
- if (Array.isArray(rawValue)) {
83
- for (const value of rawValue) {
84
- searchParams.append(key, String(value))
85
- }
86
- } else if (typeof rawValue !== "undefined") {
87
- searchParams.append(key, String(rawValue))
88
- }
89
- }
90
- return searchParams.toString()
91
- }
92
- splitUriQuery(uri) {
93
- if (typeof uri !== "string" || uri.length === 0) {
94
- return {
95
- uri,
96
- query: "",
97
- input: {}
98
- }
99
- }
100
- const queryIndex = uri.indexOf("?")
101
- if (queryIndex === -1) {
102
- return {
103
- uri,
104
- query: "",
105
- input: {}
106
- }
107
- }
108
- const query = uri.slice(queryIndex + 1)
109
- return {
110
- uri: uri.slice(0, queryIndex),
111
- query,
112
- input: this.parseQueryString(query)
113
- }
114
- }
115
- mergeRequestInput(uriInput, requestInput) {
116
- const merged = {}
117
- const sources = [uriInput, requestInput]
118
- for (const source of sources) {
119
- if (!source || typeof source !== "object") {
120
- continue
121
- }
122
- for (const [key, value] of Object.entries(source)) {
123
- merged[key] = value
124
- }
125
- }
126
- return merged
127
- }
128
- normalizeMenuSelector(selector) {
129
- if (typeof selector !== "string") {
130
- return null
131
- }
132
- const trimmed = selector.trim()
133
- if (!trimmed) {
134
- return null
135
- }
136
- const { uri, input } = this.splitUriQuery(trimmed)
137
- return {
138
- href: uri,
139
- params: input
140
- }
141
- }
142
- normalizeMenuItem(item) {
143
- if (!item || typeof item.href !== "string") {
144
- return null
145
- }
146
- const href = item.href.trim()
147
- if (!href) {
148
- return null
149
- }
150
- const { uri, input } = this.splitUriQuery(href)
151
- const params = Object.assign({}, input)
152
- if (item.params && typeof item.params === "object") {
153
- for (const [key, value] of Object.entries(item.params)) {
154
- params[key] = value
155
- }
156
- }
157
- return {
158
- item,
159
- href: uri,
160
- params
161
- }
162
- }
163
- menuSelectorMatches(selector, normalizedItem) {
164
- const normalizedSelector = this.normalizeMenuSelector(selector)
165
- if (!normalizedSelector || !normalizedItem) {
166
- return false
167
- }
168
- if (normalizedSelector.href !== normalizedItem.href) {
169
- return false
170
- }
171
- for (const [key, expectedRaw] of Object.entries(normalizedSelector.params || {})) {
172
- if (!Object.prototype.hasOwnProperty.call(normalizedItem.params || {}, key)) {
173
- return false
174
- }
175
- const actualRaw = normalizedItem.params[key]
176
- const expectedValues = Array.isArray(expectedRaw) ? expectedRaw.map((value) => String(value)) : [String(expectedRaw)]
177
- const actualValues = Array.isArray(actualRaw) ? actualRaw.map((value) => String(value)) : [String(actualRaw)]
178
- if (expectedValues.length !== actualValues.length) {
179
- return false
180
- }
181
- for (let i = 0; i < expectedValues.length; i++) {
182
- if (expectedValues[i] !== actualValues[i]) {
183
- return false
184
- }
185
- }
186
- }
187
- return true
188
- }
189
- flattenMenuItems(menu, flattened = []) {
190
- if (!Array.isArray(menu)) {
191
- return flattened
192
- }
193
- for (const item of menu) {
194
- if (!item || typeof item !== "object") {
195
- continue
196
- }
197
- const normalized = this.normalizeMenuItem(item)
198
- if (normalized) {
199
- flattened.push(normalized)
200
- }
201
- if (Array.isArray(item.menu)) {
202
- this.flattenMenuItems(item.menu, flattened)
203
- }
204
- }
205
- return flattened
206
- }
207
- async launcherMenuItems(repo_path) {
208
- let launcher = await this.launcher({
209
- path: repo_path
210
- })
211
- let config = launcher.script
212
- if (!config || !config.menu) {
213
- return []
214
- }
215
- if (typeof config.menu === "function") {
216
- if (config.menu.constructor.name === "AsyncFunction") {
217
- config.menu = await config.menu(this.kernel, this.kernel.info)
218
- } else {
219
- config.menu = config.menu(this.kernel, this.kernel.info)
220
- }
221
- }
222
- return this.flattenMenuItems(config.menu)
223
- }
224
- resolveMenuItemHref(repo_path, normalizedItem) {
225
- if (!normalizedItem || !normalizedItem.href) {
226
- return undefined
227
- }
228
- const query = this.stringifyQueryParams(normalizedItem.params)
229
- const resolvedHref = normalizedItem.href.startsWith("http")
230
- ? normalizedItem.href
231
- : path.resolve(repo_path, normalizedItem.href)
232
- if (query && query.length > 0) {
233
- return `${resolvedHref}?${query}`
234
- }
235
- return resolvedHref
236
- }
237
- isRunningMenuScript(repo_path, normalizedItem) {
238
- if (!normalizedItem || !normalizedItem.href || normalizedItem.href.startsWith("http")) {
239
- return false
240
- }
241
- const scriptPath = path.isAbsolute(normalizedItem.href)
242
- ? normalizedItem.href
243
- : path.resolve(repo_path, normalizedItem.href)
244
- return Boolean(this.kernel.status(scriptPath))
245
- }
246
31
  async launcher_path(name) {
247
32
  let root_path = this.kernel.path("api", name)
248
33
  let primary_path = path.resolve(root_path, "pinokio.js")
@@ -810,14 +595,11 @@ class Api {
810
595
  modpath = this.resolveGitURI(uri)
811
596
  } else if (uri.startsWith("~/")) {
812
597
  // absolute path
813
- let { uri: strippedUri } = this.splitUriQuery(uri)
814
- modpath = path.resolve(this.kernel.homedir, strippedUri.slice(2))
598
+ modpath = path.resolve(this.kernel.homedir, uri.slice(2))
815
599
  } else if (path.isAbsolute(uri)) {
816
- let { uri: strippedUri } = this.splitUriQuery(uri)
817
- modpath = strippedUri
600
+ modpath = uri
818
601
  } else if (cwd) {
819
- let { uri: strippedUri } = this.splitUriQuery(uri)
820
- modpath = path.resolve(cwd, strippedUri)
602
+ modpath = path.resolve(cwd, uri)
821
603
  } else {
822
604
  throw new Error("uri must be either an http uri or start with ~/")
823
605
  }
@@ -834,16 +616,13 @@ class Api {
834
616
  modpath = this.resolveGitURI(uri)
835
617
  } else if (uri.startsWith("~/")) {
836
618
  // absolute path
837
- let { uri: strippedUri } = this.splitUriQuery(uri)
838
- modpath = path.resolve(this.kernel.homedir, strippedUri.slice(2))
619
+ modpath = path.resolve(this.kernel.homedir, uri.slice(2))
839
620
  } else if (path.isAbsolute(uri)) {
840
- let { uri: strippedUri } = this.splitUriQuery(uri)
841
- modpath = strippedUri
621
+ modpath = uri
842
622
  } else {
843
623
  if (cwd) {
844
624
  // relative path against the cwd (current execution path)
845
- let { uri: strippedUri } = this.splitUriQuery(uri)
846
- modpath = path.resolve(cwd, strippedUri)
625
+ modpath = path.resolve(cwd, uri)
847
626
  } else {
848
627
  throw new Error("resolving relative paths require an additional cwd argument")
849
628
  }
@@ -1653,38 +1432,34 @@ class Api {
1653
1432
  this.process(request, resolve)
1654
1433
  })
1655
1434
  }
1656
- async get_default(repo_path, preferred = []) {
1657
- const menuItems = await this.launcherMenuItems(repo_path)
1658
- if (menuItems.length === 0) {
1659
- return undefined
1660
- }
1661
- const defaultItem = menuItems.find((item) => {
1662
- return item.item && item.item.default
1663
- })
1664
- if (defaultItem) {
1665
- return this.resolveMenuItemHref(repo_path, defaultItem)
1666
- }
1667
- const preferredSelectors = Array.isArray(preferred)
1668
- ? preferred.filter((value) => typeof value === "string" && value.trim().length > 0)
1669
- : (typeof preferred === "string" && preferred.trim().length > 0 ? [preferred] : [])
1670
- if (preferredSelectors.length === 0) {
1671
- return undefined
1672
- }
1673
- const readyUrl = menuItems.find((item) => {
1674
- return item.href && item.href.startsWith("http")
1435
+ async get_default(repo_path) {
1436
+ let launcher = await this.launcher({
1437
+ path: repo_path
1675
1438
  })
1676
- for (const selector of preferredSelectors) {
1677
- for (const item of menuItems) {
1678
- if (!this.menuSelectorMatches(selector, item)) {
1679
- continue
1439
+ let config = launcher.script
1440
+ if (config && config.menu) {
1441
+ if (typeof config.menu === "function") {
1442
+ if (config.menu.constructor.name === "AsyncFunction") {
1443
+ config.menu = await config.menu(this.kernel, this.kernel.info)
1444
+ } else {
1445
+ config.menu = config.menu(this.kernel, this.kernel.info)
1680
1446
  }
1681
- if (readyUrl && this.isRunningMenuScript(repo_path, item)) {
1682
- return this.resolveMenuItemHref(repo_path, readyUrl)
1447
+ }
1448
+ let running = config.menu.filter((item) => {
1449
+ return item.default
1450
+ })
1451
+ if (running.length > 0) {
1452
+ let r = running[0]
1453
+ if (r.href) {
1454
+ if (r.href.startsWith("http")) {
1455
+ return r.href
1456
+ } else {
1457
+ let href = path.resolve(repo_path, r.href)
1458
+ return href
1459
+ }
1683
1460
  }
1684
- return this.resolveMenuItemHref(repo_path, item)
1685
1461
  }
1686
1462
  }
1687
- return undefined
1688
1463
  }
1689
1464
  async process(request, done) {
1690
1465
  /**************************************************************
@@ -1740,13 +1515,6 @@ class Api {
1740
1515
  this.counter++;
1741
1516
  // API Call
1742
1517
 
1743
- const uriInput = request.uri.startsWith("http")
1744
- ? {}
1745
- : this.splitUriQuery(request.uri).input
1746
- if (Object.keys(uriInput).length > 0) {
1747
- request.input = this.mergeRequestInput(uriInput, request.input)
1748
- }
1749
-
1750
1518
  request.path = this.resolvePath(this.userdir, request.uri)
1751
1519
 
1752
1520
 
@@ -0,0 +1,150 @@
1
+ const path = require("path")
2
+
3
+ const filterSelectors = (preferred) => {
4
+ if (Array.isArray(preferred)) {
5
+ return preferred.filter((value) => typeof value === "string" && value.trim().length > 0)
6
+ }
7
+ if (typeof preferred === "string" && preferred.trim().length > 0) {
8
+ return [preferred]
9
+ }
10
+ return []
11
+ }
12
+
13
+ const appendInputValue = (input, key, value) => {
14
+ if (Object.prototype.hasOwnProperty.call(input, key)) {
15
+ if (Array.isArray(input[key])) {
16
+ input[key].push(value)
17
+ } else {
18
+ input[key] = [input[key], value]
19
+ }
20
+ } else {
21
+ input[key] = value
22
+ }
23
+ }
24
+
25
+ const menuTarget = (item) => {
26
+ if (!item || typeof item.href !== "string") {
27
+ return null
28
+ }
29
+ let href = item.href.trim()
30
+ if (!href) {
31
+ return null
32
+ }
33
+ let queryIndex = href.indexOf("?")
34
+ let uri = queryIndex >= 0 ? href.slice(0, queryIndex) : href
35
+ let searchParams = new URLSearchParams(queryIndex >= 0 ? href.slice(queryIndex + 1) : "")
36
+ if (item.params && typeof item.params === "object") {
37
+ for (let [key, rawValue] of Object.entries(item.params)) {
38
+ if (Array.isArray(rawValue)) {
39
+ for (let value of rawValue) {
40
+ searchParams.append(key, String(value))
41
+ }
42
+ } else if (typeof rawValue !== "undefined") {
43
+ searchParams.append(key, String(rawValue))
44
+ }
45
+ }
46
+ }
47
+ let input = {}
48
+ for (let [key, value] of searchParams.entries()) {
49
+ appendInputValue(input, key, value)
50
+ }
51
+ return {
52
+ uri,
53
+ href: searchParams.toString() ? `${uri}?${searchParams.toString()}` : uri,
54
+ input,
55
+ }
56
+ }
57
+
58
+ const selectorMatches = (selector, target) => {
59
+ if (typeof selector !== "string" || !target) {
60
+ return false
61
+ }
62
+ let candidate = selector.trim()
63
+ if (!candidate) {
64
+ return false
65
+ }
66
+ let queryIndex = candidate.indexOf("?")
67
+ let candidateUri = queryIndex >= 0 ? candidate.slice(0, queryIndex) : candidate
68
+ if (candidateUri !== target.uri) {
69
+ return false
70
+ }
71
+ if (queryIndex === -1) {
72
+ return true
73
+ }
74
+ let selectorParams = new URLSearchParams(candidate.slice(queryIndex + 1))
75
+ for (let key of new Set(selectorParams.keys())) {
76
+ if (!Object.prototype.hasOwnProperty.call(target.input, key)) {
77
+ return false
78
+ }
79
+ let expected = selectorParams.getAll(key)
80
+ let actual = Array.isArray(target.input[key]) ? target.input[key] : [target.input[key]]
81
+ if (expected.length !== actual.length) {
82
+ return false
83
+ }
84
+ for (let i = 0; i < expected.length; i++) {
85
+ if (expected[i] !== actual[i]) {
86
+ return false
87
+ }
88
+ }
89
+ }
90
+ return true
91
+ }
92
+
93
+ const loadMenu = async (api, repoPath) => {
94
+ let launcher = await api.launcher({
95
+ path: repoPath
96
+ })
97
+ let config = launcher.script
98
+ if (!config || !config.menu) {
99
+ return []
100
+ }
101
+ if (typeof config.menu === "function") {
102
+ if (config.menu.constructor.name === "AsyncFunction") {
103
+ config.menu = await config.menu(api.kernel, api.kernel.info)
104
+ } else {
105
+ config.menu = config.menu(api.kernel, api.kernel.info)
106
+ }
107
+ }
108
+ return Array.isArray(config.menu) ? config.menu : []
109
+ }
110
+
111
+ module.exports = async (api, repoPath, preferred = []) => {
112
+ let defaultTarget = await api.get_default(repoPath)
113
+ if (defaultTarget) {
114
+ return {
115
+ uri: defaultTarget
116
+ }
117
+ }
118
+ let selectors = filterSelectors(preferred)
119
+ if (selectors.length === 0) {
120
+ return undefined
121
+ }
122
+ let stack = [...(await loadMenu(api, repoPath))]
123
+ while (stack.length > 0) {
124
+ let item = stack.shift()
125
+ if (!item || typeof item !== "object") {
126
+ continue
127
+ }
128
+ if (Array.isArray(item.menu)) {
129
+ stack.unshift(...item.menu)
130
+ }
131
+ let target = menuTarget(item)
132
+ if (!target) {
133
+ continue
134
+ }
135
+ for (let selector of selectors) {
136
+ if (!selectorMatches(selector, target)) {
137
+ continue
138
+ }
139
+ if (target.uri.startsWith("http")) {
140
+ return {
141
+ uri: target.href
142
+ }
143
+ }
144
+ return {
145
+ uri: path.resolve(repoPath, target.uri),
146
+ input: Object.keys(target.input).length > 0 ? target.input : undefined
147
+ }
148
+ }
149
+ }
150
+ }
package/kernel/bin/cli.js CHANGED
@@ -2,7 +2,7 @@ const path = require('path')
2
2
  const semver = require('semver')
3
3
  const Util = require('../util')
4
4
  class CLI {
5
- version = ">=0.0.19"
5
+ version = ">=0.0.21"
6
6
  async install(req, ondata) {
7
7
  await this.kernel.exec({
8
8
  message: "npm install -g pterm@latest --force",
@@ -21,7 +21,8 @@ class CLI {
21
21
  ? this.kernel.path("bin/npm/node_modules")
22
22
  : this.kernel.path("bin/npm/lib/node_modules")
23
23
  const pkgPath = require.resolve("pterm/package.json", { paths: [moduleRoot] })
24
- const { version } = require(pkgPath)
24
+ const { resolved } = await this.kernel.loader.load(pkgPath)
25
+ const { version } = resolved || {}
25
26
  const coerced = semver.coerce(version)
26
27
  if (coerced && semver.satisfies(coerced, this.version)) {
27
28
  return true
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "6.0.86",
3
+ "version": "6.0.89",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -7734,6 +7734,22 @@ class Server {
7734
7734
  if (typeof startCommand !== "string" || startCommand.trim().length === 0) {
7735
7735
  failStart(500, `No start command configured for ${provider.label || provider.key || "provider"}.`)
7736
7736
  }
7737
+ const managedLaunchOverrides = {
7738
+ shell: null,
7739
+ env: {}
7740
+ }
7741
+ const isWindowsPlatform = (this.kernel && typeof this.kernel.platform === "string"
7742
+ ? this.kernel.platform
7743
+ : process.platform) === "win32"
7744
+ if (isWindowsPlatform && (provider.key === "codex" || provider.key === "claude")) {
7745
+ const gitBashPath = this.kernel.path("bin/miniconda/Library/bin/bash.exe")
7746
+ const bashExists = await fs.promises.access(gitBashPath, fs.constants.F_OK).then(() => true).catch(() => false)
7747
+ if (bashExists) {
7748
+ if (provider.key === "claude") {
7749
+ managedLaunchOverrides.env.CLAUDE_CODE_GIT_BASH_PATH = gitBashPath
7750
+ }
7751
+ }
7752
+ }
7737
7753
  const now = new Date()
7738
7754
  const pad = (value) => String(value).padStart(2, "0")
7739
7755
  const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
@@ -7920,6 +7936,17 @@ class Server {
7920
7936
  params.set("terminal_id", terminalId)
7921
7937
  params.set("message", startCommand)
7922
7938
  params.set("input", "1")
7939
+ if (managedLaunchOverrides.shell) {
7940
+ params.set("shell", managedLaunchOverrides.shell)
7941
+ }
7942
+ const managedLaunchEnvEntries = Object.entries(managedLaunchOverrides.env)
7943
+ for (let i = 0; i < managedLaunchEnvEntries.length; i++) {
7944
+ const [key, value] = managedLaunchEnvEntries[i]
7945
+ if (typeof key !== "string" || typeof value !== "string" || !key || !value) {
7946
+ continue
7947
+ }
7948
+ params.set(`env.${key}`, value)
7949
+ }
7923
7950
  const safeWorkspaceName = workspaceFolderName && workspaceFolderName.trim().length > 0
7924
7951
  ? workspaceFolderName.replace(/[^A-Za-z0-9._-]+/g, "-")
7925
7952
  : "workspace"
@@ -8993,6 +9020,14 @@ class Server {
8993
9020
  }
8994
9021
  //let message = req.query.message ? req.query.message : null
8995
9022
  let venv = req.query.venv ? decodeURIComponent(req.query.venv) : null
9023
+ let launchShell = null
9024
+ if (typeof req.query.shell === "string" && req.query.shell.length > 0) {
9025
+ try {
9026
+ launchShell = decodeURIComponent(req.query.shell)
9027
+ } catch (error) {
9028
+ launchShell = req.query.shell
9029
+ }
9030
+ }
8996
9031
  let input = req.query.input ? true : false
8997
9032
  let callback = req.query.callback ? decodeURIComponent(req.query.callback) : null
8998
9033
  let callback_target = req.query.callback_target ? decodeURIComponent(req.query.callback_target) : null
@@ -9037,6 +9072,7 @@ class Server {
9037
9072
  message,
9038
9073
  restart_message: restartMessage,
9039
9074
  venv,
9075
+ launch_shell: launchShell,
9040
9076
  conda: (conda_exists ? conda: null),
9041
9077
  env,
9042
9078
  // pattern,
package/server/socket.js CHANGED
@@ -5,6 +5,7 @@ const os = require('os')
5
5
  const fs = require('fs')
6
6
  const Util = require("../kernel/util")
7
7
  const Environment = require("../kernel/environment")
8
+ const getLaunchTarget = require("../kernel/api/launcher_target")
8
9
  const NOTIFICATION_CHANNEL = 'kernel.notifications'
9
10
  class Socket {
10
11
  normalizeLaunchSource(source = "") {
@@ -196,10 +197,11 @@ class Socket {
196
197
  // get the default script and respond
197
198
  let id = this.parent.kernel.api.filePath(req.uri)
198
199
  try {
199
- let default_url = await this.parent.kernel.api.get_default(id, req.default)
200
+ let defaultTarget = await getLaunchTarget(this.parent.kernel.api, id, req.default)
200
201
  ws.send(JSON.stringify({
201
202
  data: {
202
- uri: default_url
203
+ uri: defaultTarget ? defaultTarget.uri : undefined,
204
+ input: defaultTarget ? defaultTarget.input : undefined
203
205
  }
204
206
  }))
205
207
  } catch (e) {
@@ -568,6 +568,9 @@ document.addEventListener("DOMContentLoaded", async () => {
568
568
  <% if (venv) { %>
569
569
  venv: "<%-JSON.stringify(venv).slice(1, -1)%>",
570
570
  <% } %>
571
+ <% if (launch_shell) { %>
572
+ shell: "<%-JSON.stringify(launch_shell).slice(1, -1)%>",
573
+ <% } %>
571
574
  <% if (kill) { %>
572
575
  on: [{
573
576
  event: "<%=kill%>",
@@ -9415,16 +9415,16 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
9415
9415
  </div>
9416
9416
  </header>` : ""}
9417
9417
  <div class="terminals-chooser-content">
9418
+ <section class="terminals-workspaces-header" aria-label="Agent workspaces intro">
9419
+ <div class="terminals-workspace-header-actions terminals-workspaces-header-actions" data-workspaces-header-actions></div>
9420
+ </section>
9421
+ <section class="terminals-workspace-sessions-header hidden" aria-label="Workspace sessions intro">
9422
+ <div class="terminals-list-intro-breadcrumb terminals-workspace-header-breadcrumb" data-workspace-header-breadcrumb aria-label="Workspace breadcrumb"></div>
9423
+ <p data-workspace-header-path></p>
9424
+ <div class="terminals-workspace-header-actions" data-workspace-header-actions></div>
9425
+ </section>
9418
9426
  <div class="terminals-empty" data-state="loading" aria-live="polite"></div>
9419
9427
  <div class="terminals-list-wrap">
9420
- <section class="terminals-workspaces-header" aria-label="Agent workspaces intro">
9421
- <div class="terminals-workspace-header-actions terminals-workspaces-header-actions" data-workspaces-header-actions></div>
9422
- </section>
9423
- <section class="terminals-workspace-sessions-header hidden" aria-label="Workspace sessions intro">
9424
- <div class="terminals-list-intro-breadcrumb terminals-workspace-header-breadcrumb" data-workspace-header-breadcrumb aria-label="Workspace breadcrumb"></div>
9425
- <p data-workspace-header-path></p>
9426
- <div class="terminals-workspace-header-actions" data-workspace-header-actions></div>
9427
- </section>
9428
9428
  <div class="terminals-list"></div>
9429
9429
  </div>
9430
9430
  </div>