pinokiod 6.0.41 → 6.0.43

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/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.14"
5
+ version = ">=0.0.17"
6
6
  async install(req, ondata) {
7
7
  await this.kernel.exec({
8
8
  message: "npm install -g pterm@latest --force",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinokiod",
3
- "version": "6.0.41",
3
+ "version": "6.0.43",
4
4
  "description": "",
5
5
  "main": "index.js",
6
6
  "scripts": {
package/server/index.js CHANGED
@@ -6397,6 +6397,8 @@ class Server {
6397
6397
  })
6398
6398
  const {
6399
6399
  getTerminalStarterProviders,
6400
+ normalizeTerminalLaunchMode,
6401
+ buildTerminalStartCommand,
6400
6402
  getTerminalWorkspacesRoot,
6401
6403
  isValidTerminalWorkspaceName,
6402
6404
  listTerminalWorkspaceFolders,
@@ -7526,6 +7528,16 @@ class Server {
7526
7528
  }
7527
7529
 
7528
7530
  const provider = providerMap.get(providerKey)
7531
+ const launchMode = normalizeTerminalLaunchMode(
7532
+ body && typeof body === "object"
7533
+ ? (body.launchMode || body.launch_mode)
7534
+ : "",
7535
+ provider && provider.defaultLaunchMode ? provider.defaultLaunchMode : "guarded"
7536
+ )
7537
+ const startCommand = buildTerminalStartCommand(provider, launchMode) || provider.startCommand || provider.command
7538
+ if (typeof startCommand !== "string" || startCommand.trim().length === 0) {
7539
+ failStart(500, `No start command configured for ${provider.label || provider.key || "provider"}.`)
7540
+ }
7529
7541
  const now = new Date()
7530
7542
  const pad = (value) => String(value).padStart(2, "0")
7531
7543
  const timestamp = `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`
@@ -7692,7 +7704,8 @@ class Server {
7692
7704
  label: provider.label,
7693
7705
  terminal_id: terminalId,
7694
7706
  created_at: now.toISOString(),
7695
- command: provider.startCommand || provider.command,
7707
+ launch_mode: launchMode,
7708
+ command: startCommand,
7696
7709
  skill_context: skillContext && skillContext.activePath ? skillContext.activePath : null,
7697
7710
  skills: skillContext && Array.isArray(skillContext.selected) ? skillContext.selected : [],
7698
7711
  uploaded_files: copiedUploads
@@ -7709,7 +7722,7 @@ class Server {
7709
7722
  params.set("path", sessionCwd)
7710
7723
  params.set("cwd", sessionCwd)
7711
7724
  params.set("terminal_id", terminalId)
7712
- params.set("message", provider.startCommand || provider.command)
7725
+ params.set("message", startCommand)
7713
7726
  params.set("input", "1")
7714
7727
  const safeWorkspaceName = workspaceFolderName && workspaceFolderName.trim().length > 0
7715
7728
  ? workspaceFolderName.replace(/[^A-Za-z0-9._-]+/g, "-")
@@ -7739,7 +7752,9 @@ class Server {
7739
7752
  workspace_path: sessionCwd || "",
7740
7753
  summary: null,
7741
7754
  timestamp: now.toISOString(),
7742
- terminal_id: terminalId
7755
+ terminal_id: terminalId,
7756
+ launch_mode: launchMode,
7757
+ command: startCommand
7743
7758
  }
7744
7759
  await upsertTerminalSessionRegistryEntry(optimisticEntry)
7745
7760
  return {
@@ -7754,6 +7769,7 @@ class Server {
7754
7769
  index: `launch:${terminalId}`,
7755
7770
  uri: `${provider.key}:launch:${terminalId}`,
7756
7771
  timestamp: now.toISOString(),
7772
+ launch_mode: launchMode,
7757
7773
  url,
7758
7774
  skills: skillContext && Array.isArray(skillContext.selected)
7759
7775
  ? skillContext.selected.map((skill) => ({ id: skill.id, label: skill.label }))
@@ -8747,12 +8763,14 @@ class Server {
8747
8763
  }
8748
8764
  let restartMessage = message
8749
8765
  const routeProviderKey = extractProviderFromRouteId(decodedRouteId)
8750
- if (routeProviderKey) {
8766
+ const hasExplicitRestartMessage = typeof restartMessage === "string" && restartMessage.trim().length > 0
8767
+ if (routeProviderKey && !hasExplicitRestartMessage) {
8751
8768
  const restartProvider = getTerminalStarterProviders().find((provider) => normalizeProviderKey(provider && provider.key) === routeProviderKey)
8752
8769
  if (restartProvider) {
8753
- const restartCandidate = typeof restartProvider.startCommand === "string" && restartProvider.startCommand.trim().length > 0
8754
- ? restartProvider.startCommand.trim()
8755
- : (typeof restartProvider.command === "string" ? restartProvider.command.trim() : "")
8770
+ const restartCandidate = buildTerminalStartCommand(
8771
+ restartProvider,
8772
+ restartProvider && restartProvider.defaultLaunchMode ? restartProvider.defaultLaunchMode : "guarded"
8773
+ )
8756
8774
  if (restartCandidate) {
8757
8775
  restartMessage = restartCandidate
8758
8776
  }
@@ -3,21 +3,83 @@
3
3
  const { createTerminalSessionRegistry } = require("./terminal_session_registry")
4
4
 
5
5
  const createTerminalSessionHelpers = ({ kernel, fs, path, os, crypto }) => {
6
+ const TERMINAL_LAUNCH_MODE_GUARDED = "guarded"
7
+ const TERMINAL_LAUNCH_MODE_YOLO = "yolo"
8
+ const normalizeTerminalLaunchMode = (value, fallback = TERMINAL_LAUNCH_MODE_GUARDED) => {
9
+ const normalizedFallback = typeof fallback === "string" && fallback.trim().toLowerCase() === TERMINAL_LAUNCH_MODE_YOLO
10
+ ? TERMINAL_LAUNCH_MODE_YOLO
11
+ : TERMINAL_LAUNCH_MODE_GUARDED
12
+ const normalized = typeof value === "string" ? value.trim().toLowerCase() : ""
13
+ if (!normalized) {
14
+ return normalizedFallback
15
+ }
16
+ if (
17
+ normalized === TERMINAL_LAUNCH_MODE_YOLO
18
+ || normalized === "danger"
19
+ || normalized === "dangerous"
20
+ || normalized === "true-yolo"
21
+ ) {
22
+ return TERMINAL_LAUNCH_MODE_YOLO
23
+ }
24
+ if (
25
+ normalized === TERMINAL_LAUNCH_MODE_GUARDED
26
+ || normalized === "default"
27
+ || normalized === "safe"
28
+ || normalized === "standard"
29
+ ) {
30
+ return TERMINAL_LAUNCH_MODE_GUARDED
31
+ }
32
+ return normalizedFallback
33
+ }
34
+ const buildTerminalStartCommand = (provider, launchMode = TERMINAL_LAUNCH_MODE_GUARDED) => {
35
+ if (!provider || typeof provider !== "object") {
36
+ return ""
37
+ }
38
+ const mode = normalizeTerminalLaunchMode(launchMode, provider.defaultLaunchMode || TERMINAL_LAUNCH_MODE_GUARDED)
39
+ const startCommands = provider.startCommands && typeof provider.startCommands === "object"
40
+ ? provider.startCommands
41
+ : null
42
+ if (startCommands && typeof startCommands[mode] === "string" && startCommands[mode].trim().length > 0) {
43
+ return startCommands[mode].trim()
44
+ }
45
+ if (typeof provider.startCommand === "string" && provider.startCommand.trim().length > 0) {
46
+ return provider.startCommand.trim()
47
+ }
48
+ if (typeof provider.command === "string" && provider.command.trim().length > 0) {
49
+ return provider.command.trim()
50
+ }
51
+ return ""
52
+ }
6
53
  const getTerminalStarterProviders = () => {
7
54
  return [{
8
55
  key: "codex",
9
56
  label: "Codex",
10
57
  command: "npx -y @openai/codex@latest",
58
+ defaultLaunchMode: TERMINAL_LAUNCH_MODE_GUARDED,
59
+ startCommands: {
60
+ [TERMINAL_LAUNCH_MODE_GUARDED]: 'npx -y @openai/codex@latest -c shell_environment_policy.inherit="all" --sandbox workspace-write --full-auto --ask-for-approval never',
61
+ [TERMINAL_LAUNCH_MODE_YOLO]: "npx -y @openai/codex@latest --dangerously-bypass-approvals-and-sandbox"
62
+ },
11
63
  startCommand: 'npx -y @openai/codex@latest -c shell_environment_policy.inherit="all" --sandbox workspace-write --full-auto --ask-for-approval never'
12
64
  }, {
13
65
  key: "claude",
14
66
  label: "Claude",
15
67
  command: "npx -y @anthropic-ai/claude-code@latest",
68
+ defaultLaunchMode: TERMINAL_LAUNCH_MODE_GUARDED,
69
+ startCommands: {
70
+ [TERMINAL_LAUNCH_MODE_GUARDED]: "npx -y @anthropic-ai/claude-code@latest",
71
+ [TERMINAL_LAUNCH_MODE_YOLO]: "npx -y @anthropic-ai/claude-code@latest --dangerously-skip-permissions"
72
+ },
16
73
  startCommand: "npx -y @anthropic-ai/claude-code@latest"
17
74
  }, {
18
75
  key: "gemini",
19
76
  label: "Gemini",
20
77
  command: "npx -y @google/gemini-cli@latest",
78
+ defaultLaunchMode: TERMINAL_LAUNCH_MODE_GUARDED,
79
+ startCommands: {
80
+ [TERMINAL_LAUNCH_MODE_GUARDED]: "npx -y @google/gemini-cli@latest",
81
+ [TERMINAL_LAUNCH_MODE_YOLO]: "npx -y @google/gemini-cli@latest --approval-mode yolo --no-sandbox"
82
+ },
21
83
  startCommand: "npx -y @google/gemini-cli@latest"
22
84
  }]
23
85
  }
@@ -2524,6 +2586,8 @@ const createTerminalSessionHelpers = ({ kernel, fs, path, os, crypto }) => {
2524
2586
 
2525
2587
  return {
2526
2588
  getTerminalStarterProviders,
2589
+ normalizeTerminalLaunchMode,
2590
+ buildTerminalStartCommand,
2527
2591
  getTerminalWorkspacesRoot,
2528
2592
  isValidTerminalWorkspaceName,
2529
2593
  listTerminalWorkspaceFolders,
@@ -1428,6 +1428,66 @@ body.dark .terminals-launcher-search {
1428
1428
  flex-wrap: nowrap;
1429
1429
  }
1430
1430
 
1431
+ .terminals-launcher-mode-options {
1432
+ display: inline-flex;
1433
+ gap: 6px;
1434
+ align-items: center;
1435
+ }
1436
+
1437
+ .terminals-launcher-mode {
1438
+ display: inline-flex;
1439
+ align-items: center;
1440
+ justify-content: center;
1441
+ gap: 6px;
1442
+ height: var(--terminals-launcher-control-height);
1443
+ min-width: 90px;
1444
+ padding: 0 12px;
1445
+ border-radius: 7px;
1446
+ border: 1px solid rgba(15, 23, 42, 0.16);
1447
+ background: transparent;
1448
+ color: inherit;
1449
+ cursor: pointer;
1450
+ font-size: 12px;
1451
+ font-weight: 700;
1452
+ transition: border-color 0.12s ease, background 0.12s ease, color 0.12s ease;
1453
+ }
1454
+
1455
+ body.dark .terminals-launcher-mode {
1456
+ border: 1px solid rgba(255, 255, 255, 0.18);
1457
+ }
1458
+
1459
+ .terminals-launcher-mode.active {
1460
+ border-color: rgba(65, 105, 225, 0.72);
1461
+ background: rgba(65, 105, 225, 0.12);
1462
+ }
1463
+
1464
+ .terminals-launcher-mode:not(.active):hover {
1465
+ border-color: rgba(65, 105, 225, 0.68);
1466
+ background: rgba(65, 105, 225, 0.06);
1467
+ }
1468
+
1469
+ .terminals-launcher-mode.is-danger.active {
1470
+ border-color: rgba(220, 38, 38, 0.72);
1471
+ background: rgba(220, 38, 38, 0.13);
1472
+ color: rgba(127, 29, 29, 0.96);
1473
+ }
1474
+
1475
+ .terminals-launcher-mode.is-danger:not(.active):hover {
1476
+ border-color: rgba(220, 38, 38, 0.68);
1477
+ background: rgba(220, 38, 38, 0.08);
1478
+ }
1479
+
1480
+ body.dark .terminals-launcher-mode.active {
1481
+ border-color: rgba(95, 130, 235, 0.75);
1482
+ background: rgba(95, 130, 235, 0.14);
1483
+ }
1484
+
1485
+ body.dark .terminals-launcher-mode.is-danger.active {
1486
+ border-color: rgba(248, 113, 113, 0.74);
1487
+ background: rgba(248, 113, 113, 0.16);
1488
+ color: rgba(254, 226, 226, 0.95);
1489
+ }
1490
+
1431
1491
  .terminals-launcher-provider {
1432
1492
  display: inline-flex;
1433
1493
  align-items: center;
@@ -2899,6 +2959,23 @@ body.dark .terminals-workspace-create-input.swal2-input {
2899
2959
  padding: 0 !important;
2900
2960
  }
2901
2961
 
2962
+ .terminals-workspace-create-actions.swal2-actions.swal2-loading {
2963
+ justify-content: flex-end !important;
2964
+ gap: 8px !important;
2965
+ }
2966
+
2967
+ .terminals-workspace-create-actions.swal2-actions .swal2-loader {
2968
+ width: 18px !important;
2969
+ height: 18px !important;
2970
+ margin: 0 !important;
2971
+ border-width: 2px !important;
2972
+ border-color: #3b82f6 rgba(0, 0, 0, 0) #3b82f6 rgba(0, 0, 0, 0) !important;
2973
+ }
2974
+
2975
+ body.dark .terminals-workspace-create-actions.swal2-actions .swal2-loader {
2976
+ border-color: #60a5fa rgba(0, 0, 0, 0) #60a5fa rgba(0, 0, 0, 0) !important;
2977
+ }
2978
+
2902
2979
  .terminals-workspace-create-validation.swal2-validation-message {
2903
2980
  margin-top: 8px !important;
2904
2981
  border-radius: 4px !important;
@@ -4273,6 +4350,19 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
4273
4350
  <div class="terminals-launcher-provider-options" role="group" aria-label="CLI providers"></div>
4274
4351
  </div>
4275
4352
  </div>
4353
+ <div class="terminals-launcher-main-row">
4354
+ <div class="terminals-launcher-main-label">
4355
+ <div class="terminals-launcher-main-label-title">Mode</div>
4356
+ <div class="terminals-launcher-main-label-subtitle">Execution policy</div>
4357
+ </div>
4358
+ <div class="terminals-launcher-main-value">
4359
+ <div class="terminals-launcher-mode-options" role="group" aria-label="Launch mode">
4360
+ <button class="terminals-launcher-mode" type="button" data-launch-mode="guarded">Guarded</button>
4361
+ <button class="terminals-launcher-mode is-danger" type="button" data-launch-mode="yolo">YOLO</button>
4362
+ </div>
4363
+ <span class="terminals-launcher-row-meta terminals-launcher-mode-meta"></span>
4364
+ </div>
4365
+ </div>
4276
4366
  <div class="terminals-launcher-main-row">
4277
4367
  <div class="terminals-launcher-main-label">
4278
4368
  <div class="terminals-launcher-main-label-title">Skills</div>
@@ -4374,6 +4464,8 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
4374
4464
  const launcherSearchForm = launcherModal ? launcherModal.querySelector(".terminals-launcher-search-form") : null
4375
4465
  const launcherSearchInput = launcherModal ? launcherModal.querySelector(".terminals-launcher-search") : null
4376
4466
  const launcherProviderOptions = launcherModal ? launcherModal.querySelector(".terminals-launcher-provider-options") : null
4467
+ const launcherModeOptions = launcherModal ? launcherModal.querySelector(".terminals-launcher-mode-options") : null
4468
+ const launcherModeMeta = launcherModal ? launcherModal.querySelector(".terminals-launcher-mode-meta") : null
4377
4469
  const launcherAddSkillsButton = launcherModal ? launcherModal.querySelector(".terminals-launcher-add-skills") : null
4378
4470
  const launcherAddFilesButton = launcherModal ? launcherModal.querySelector(".terminals-launcher-add-files") : null
4379
4471
  const launcherBackButton = launcherModal ? launcherModal.querySelector(".terminals-launcher-back") : null
@@ -4387,12 +4479,15 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
4387
4479
  const launcherCloseButton = launcherModal ? launcherModal.querySelector(".terminals-launcher-close") : null
4388
4480
  const launcherFooter = launcherModal ? launcherModal.querySelector(".terminals-launcher-footer") : null
4389
4481
  const launcherStartButton = launcherModal ? launcherModal.querySelector(".terminals-launcher-start") : null
4482
+ const LAUNCH_MODE_GUARDED = "guarded"
4483
+ const LAUNCH_MODE_YOLO = "yolo"
4390
4484
  const launcherState = {
4391
4485
  open: false,
4392
4486
  busy: false,
4393
4487
  column: null,
4394
4488
  workspaceCwd: "",
4395
4489
  provider: String(startProviders && startProviders[0] && startProviders[0].key ? startProviders[0].key : "codex").trim().toLowerCase(),
4490
+ launchMode: LAUNCH_MODE_GUARDED,
4396
4491
  view: "main",
4397
4492
  query: "",
4398
4493
  selectedSkills: new Set(),
@@ -4432,6 +4527,9 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
4432
4527
  let sessionNextCursor = 0
4433
4528
  let sessionAppendInProgress = false
4434
4529
  let currentSessionQuery = ""
4530
+ let lastSessionRefreshWorkspaceKey = ""
4531
+ let lastSessionRefreshQuery = ""
4532
+ let autoLauncherPromptedWorkspaceKey = ""
4435
4533
  let sessionSnapshotVersion = 0
4436
4534
  let sessionSearchDebounceTimer = null
4437
4535
  let sessionFetchAbortController = null
@@ -4993,6 +5091,16 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
4993
5091
  const normalizeProviderKey = (value) => {
4994
5092
  return String(value || "").trim().toLowerCase()
4995
5093
  }
5094
+ const normalizeLaunchMode = (value, fallback = LAUNCH_MODE_GUARDED) => {
5095
+ const normalized = String(value || "").trim().toLowerCase()
5096
+ if (normalized === LAUNCH_MODE_YOLO || normalized === "danger" || normalized === "dangerous" || normalized === "true-yolo") {
5097
+ return LAUNCH_MODE_YOLO
5098
+ }
5099
+ if (normalized === LAUNCH_MODE_GUARDED || normalized === "default" || normalized === "safe" || normalized === "standard") {
5100
+ return LAUNCH_MODE_GUARDED
5101
+ }
5102
+ return fallback === LAUNCH_MODE_YOLO ? LAUNCH_MODE_YOLO : LAUNCH_MODE_GUARDED
5103
+ }
4996
5104
  const removeTemporarySessionByTerminalId = (terminalId) => {
4997
5105
  const normalizedTerminalId = normalizeTerminalId(terminalId)
4998
5106
  if (!normalizedTerminalId) {
@@ -5198,8 +5306,12 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
5198
5306
  }
5199
5307
  const setWorkspaceViewMode = (mode, workspaceKey = "") => {
5200
5308
  const normalizedMode = mode === "sessions" ? "sessions" : "workspaces"
5309
+ const normalizedWorkspaceKey = normalizedMode === "sessions" ? normalizeCwdKey(workspaceKey) : ""
5201
5310
  workspaceViewState.mode = normalizedMode
5202
- workspaceViewState.key = normalizedMode === "sessions" ? normalizeCwdKey(workspaceKey) : ""
5311
+ workspaceViewState.key = normalizedWorkspaceKey
5312
+ if (autoLauncherPromptedWorkspaceKey && (normalizedMode !== "sessions" || normalizedWorkspaceKey !== autoLauncherPromptedWorkspaceKey)) {
5313
+ autoLauncherPromptedWorkspaceKey = ""
5314
+ }
5203
5315
  if (sessionSearchInput) {
5204
5316
  sessionSearchInput.placeholder = normalizedMode === "sessions" ? "Search sessions" : "Search workspaces"
5205
5317
  }
@@ -5240,12 +5352,18 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
5240
5352
  const renderLauncherSummary = () => {
5241
5353
  const skillCount = launcherState.selectedSkills.size
5242
5354
  const fileCount = launcherState.pendingFiles.length
5355
+ const launchMode = normalizeLaunchMode(launcherState.launchMode, LAUNCH_MODE_GUARDED)
5243
5356
  if (launcherSummarySkills) {
5244
5357
  launcherSummarySkills.textContent = `Skills: ${skillCount}`
5245
5358
  }
5246
5359
  if (launcherSummaryFiles) {
5247
5360
  launcherSummaryFiles.textContent = `Files: ${fileCount}`
5248
5361
  }
5362
+ if (launcherModeMeta) {
5363
+ launcherModeMeta.textContent = launchMode === LAUNCH_MODE_YOLO
5364
+ ? "YOLO: run without approvals"
5365
+ : "Guarded: provider default approval/sandbox."
5366
+ }
5249
5367
  }
5250
5368
 
5251
5369
  const setLauncherBusy = (busy) => {
@@ -5280,6 +5398,12 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
5280
5398
  button.disabled = launcherState.busy
5281
5399
  })
5282
5400
  }
5401
+ if (launcherModeOptions) {
5402
+ const modeButtons = launcherModeOptions.querySelectorAll(".terminals-launcher-mode")
5403
+ modeButtons.forEach((button) => {
5404
+ button.disabled = launcherState.busy
5405
+ })
5406
+ }
5283
5407
  if (launcherSkillList) {
5284
5408
  const skillButtons = launcherSkillList.querySelectorAll(".terminals-launcher-skill-row")
5285
5409
  skillButtons.forEach((button) => {
@@ -5447,6 +5571,29 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
5447
5571
  }
5448
5572
  }
5449
5573
 
5574
+ const renderLauncherModeOptions = () => {
5575
+ if (!launcherModeOptions) {
5576
+ return
5577
+ }
5578
+ launcherState.launchMode = normalizeLaunchMode(launcherState.launchMode, LAUNCH_MODE_GUARDED)
5579
+ const modeButtons = launcherModeOptions.querySelectorAll(".terminals-launcher-mode")
5580
+ modeButtons.forEach((button) => {
5581
+ const mode = normalizeLaunchMode(button && button.dataset ? button.dataset.launchMode : "", LAUNCH_MODE_GUARDED)
5582
+ const isActive = mode === launcherState.launchMode
5583
+ button.classList.toggle("active", isActive)
5584
+ button.setAttribute("aria-pressed", isActive ? "true" : "false")
5585
+ button.disabled = launcherState.busy
5586
+ button.onclick = () => {
5587
+ if (launcherState.busy) {
5588
+ return
5589
+ }
5590
+ launcherState.launchMode = mode
5591
+ renderLauncherModeOptions()
5592
+ }
5593
+ })
5594
+ renderLauncherSummary()
5595
+ }
5596
+
5450
5597
  const renderLauncherSkillList = () => {
5451
5598
  if (!launcherSkillList) {
5452
5599
  return
@@ -5606,6 +5753,7 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
5606
5753
  }
5607
5754
  setLauncherView("main")
5608
5755
  renderLauncherProviderOptions()
5756
+ renderLauncherModeOptions()
5609
5757
  renderLauncherSelectedChips()
5610
5758
  renderLauncherSkillList()
5611
5759
  renderLauncherFiles()
@@ -5634,6 +5782,7 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
5634
5782
  if (!workspaceCwd) {
5635
5783
  return
5636
5784
  }
5785
+ const launchMode = normalizeLaunchMode(launcherState.launchMode, LAUNCH_MODE_GUARDED)
5637
5786
  const selectedSkillIds = Array.from(launcherState.selectedSkills)
5638
5787
  setLauncherBusy(true)
5639
5788
  let shouldClose = false
@@ -5643,7 +5792,7 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
5643
5792
  const uploaded = await uploadLauncherFiles()
5644
5793
  uploadToken = uploaded && uploaded.uploadToken ? uploaded.uploadToken : ""
5645
5794
  }
5646
- const started = await startProviderSession(column, provider, launcherStartButton, null, selectedSkillIds, uploadToken, workspaceCwd)
5795
+ const started = await startProviderSession(column, provider, launchMode, launcherStartButton, null, selectedSkillIds, uploadToken, workspaceCwd)
5647
5796
  if (started) {
5648
5797
  launcherState.pendingFiles = []
5649
5798
  renderLauncherFiles()
@@ -8043,6 +8192,8 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
8043
8192
  if (incomingSnapshotVersion > sessionSnapshotVersion) {
8044
8193
  sessionSnapshotVersion = incomingSnapshotVersion
8045
8194
  }
8195
+ lastSessionRefreshWorkspaceKey = workspaceScopeKey
8196
+ lastSessionRefreshQuery = currentSessionQuery
8046
8197
  requestSucceeded = true
8047
8198
  return true
8048
8199
  } catch (error) {
@@ -8112,8 +8263,9 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
8112
8263
  }
8113
8264
  void loadMoreSessionItems()
8114
8265
  }
8115
- const startProviderSession = async (column, providerKey, startButton, providerSelect, selectedSkillIds = [], uploadToken = "", workspacePath = "") => {
8266
+ const startProviderSession = async (column, providerKey, launchMode = LAUNCH_MODE_GUARDED, startButton, providerSelect, selectedSkillIds = [], uploadToken = "", workspacePath = "") => {
8116
8267
  const normalizedProvider = normalizeIndex(providerKey).toLowerCase()
8268
+ const normalizedLaunchMode = normalizeLaunchMode(launchMode, LAUNCH_MODE_GUARDED)
8117
8269
  if (!normalizedProvider) {
8118
8270
  return false
8119
8271
  }
@@ -8131,6 +8283,7 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
8131
8283
  },
8132
8284
  body: JSON.stringify({
8133
8285
  provider: normalizedProvider,
8286
+ launchMode: normalizedLaunchMode,
8134
8287
  skills: Array.isArray(selectedSkillIds) ? selectedSkillIds : [],
8135
8288
  uploadToken: typeof uploadToken === "string" ? uploadToken : "",
8136
8289
  workspacePath: typeof workspacePath === "string" ? workspacePath : ""
@@ -8794,6 +8947,56 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
8794
8947
  }
8795
8948
  }
8796
8949
 
8950
+ const maybeAutoOpenLauncherForEmptyWorkspace = () => {
8951
+ if (launcherState.open || launcherState.busy) {
8952
+ return false
8953
+ }
8954
+ if (workspaceViewState.mode !== "sessions") {
8955
+ return false
8956
+ }
8957
+ if (!hasAttemptedSessionLoad || lastSessionLoadFailed) {
8958
+ return false
8959
+ }
8960
+ const workspaceKey = normalizeCwdKey(workspaceViewState.key)
8961
+ if (!workspaceKey) {
8962
+ return false
8963
+ }
8964
+ if (autoLauncherPromptedWorkspaceKey === workspaceKey) {
8965
+ return false
8966
+ }
8967
+ if (normalizeCwdKey(lastSessionRefreshWorkspaceKey) !== workspaceKey) {
8968
+ return false
8969
+ }
8970
+ if (normalizeIndex(lastSessionRefreshQuery).trim().length > 0) {
8971
+ return false
8972
+ }
8973
+ const workspace = getCurrentWorkspaceItem()
8974
+ const workspaceSessions = workspace && Array.isArray(workspace.sessions) ? workspace.sessions : []
8975
+ if (!workspace || workspaceSessions.length > 0) {
8976
+ return false
8977
+ }
8978
+ const chooser = getOrCreateChooserColumn()
8979
+ if (!chooser) {
8980
+ return false
8981
+ }
8982
+ const workspacePath = workspace && workspace.cwd ? workspace.cwd : workspaceKey
8983
+ autoLauncherPromptedWorkspaceKey = workspaceKey
8984
+ requestAnimationFrame(() => {
8985
+ if (launcherState.open || launcherState.busy) {
8986
+ return
8987
+ }
8988
+ if (workspaceViewState.mode !== "sessions" || normalizeCwdKey(workspaceViewState.key) !== workspaceKey) {
8989
+ return
8990
+ }
8991
+ const liveChooser = getOrCreateChooserColumn()
8992
+ if (!liveChooser || !liveChooser.isConnected || liveChooser.dataset.state !== "chooser") {
8993
+ return
8994
+ }
8995
+ openLauncherModal(liveChooser, workspacePath)
8996
+ })
8997
+ return true
8998
+ }
8999
+
8797
9000
  const refreshChooserRows = (options = {}) => {
8798
9001
  renderChooserTopBar()
8799
9002
  const query = getSessionSearchQuery()
@@ -8805,6 +9008,7 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
8805
9008
  maybeLoadMoreSessionsForColumn(chooserColumn)
8806
9009
  }
8807
9010
  })
9011
+ maybeAutoOpenLauncherForEmptyWorkspace()
8808
9012
  }
8809
9013
 
8810
9014
  const setupChooserColumn = (column, showClose = false, refreshOptions = null) => {
@@ -9189,6 +9393,55 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
9189
9393
  workspaceFolders = next
9190
9394
  }
9191
9395
 
9396
+ const finalizeCreatedWorkspace = (createdWorkspace, fallbackName) => {
9397
+ const workspaceCwd = createdWorkspace && typeof createdWorkspace.cwd === "string"
9398
+ ? String(createdWorkspace.cwd)
9399
+ : ""
9400
+ const workspaceKey = normalizeCwdKey(workspaceCwd)
9401
+ if (!workspaceKey) {
9402
+ return false
9403
+ }
9404
+ addWorkspaceToLocalList({
9405
+ name: createdWorkspace && createdWorkspace.folder ? createdWorkspace.folder : fallbackName,
9406
+ cwd: workspaceCwd
9407
+ })
9408
+ if (sessionSearchInput) {
9409
+ sessionSearchInput.value = ""
9410
+ }
9411
+ setWorkspaceViewMode("sessions", workspaceKey)
9412
+ refreshChooserRows()
9413
+ const workspacePath = workspaceCwd || workspaceKey
9414
+ if (!launcherState.open && !launcherState.busy) {
9415
+ autoLauncherPromptedWorkspaceKey = workspaceKey
9416
+ requestAnimationFrame(() => {
9417
+ if (launcherState.open || launcherState.busy) {
9418
+ return
9419
+ }
9420
+ if (workspaceViewState.mode !== "sessions" || normalizeCwdKey(workspaceViewState.key) !== workspaceKey) {
9421
+ return
9422
+ }
9423
+ const liveChooser = getOrCreateChooserColumn()
9424
+ if (!liveChooser || !liveChooser.isConnected || liveChooser.dataset.state !== "chooser") {
9425
+ return
9426
+ }
9427
+ openLauncherModal(liveChooser, workspacePath)
9428
+ })
9429
+ }
9430
+ const refreshPromise = refreshSessionItems({
9431
+ sync: true,
9432
+ query: "",
9433
+ workspaceKey
9434
+ }).then((loaded) => {
9435
+ refreshChooserRows()
9436
+ return Boolean(loaded)
9437
+ }).catch(() => {
9438
+ refreshChooserRows()
9439
+ return false
9440
+ })
9441
+ void refreshPromise
9442
+ return true
9443
+ }
9444
+
9192
9445
  const openCreateWorkspaceModal = async () => {
9193
9446
  const suggested = await suggestWorkspaceName()
9194
9447
  let defaultName = suggested || `${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
@@ -9211,25 +9464,14 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
9211
9464
  if (!folderName) {
9212
9465
  return false
9213
9466
  }
9467
+ if (folderName.includes(" ")) {
9468
+ window.alert("Workspace name cannot contain spaces.")
9469
+ return false
9470
+ }
9214
9471
  try {
9215
9472
  const payload = await createWorkspace(folderName)
9216
- addWorkspaceToLocalList({
9217
- name: payload.folder || folderName,
9218
- cwd: payload.cwd || ""
9219
- })
9220
- if (sessionSearchInput) {
9221
- sessionSearchInput.value = ""
9222
- }
9223
- setWorkspaceViewMode("sessions", normalizeCwdKey(payload.cwd || ""))
9224
- refreshChooserRows()
9225
- void refreshSessionItems({
9226
- sync: true,
9227
- query: "",
9228
- workspaceKey: normalizeCwdKey(payload.cwd || "")
9229
- }).then(() => {
9230
- refreshChooserRows()
9231
- })
9232
- return true
9473
+ const finalized = finalizeCreatedWorkspace(payload, folderName)
9474
+ return finalized
9233
9475
  } catch (error) {
9234
9476
  window.alert(error && error.message ? error.message : "Failed to create workspace.")
9235
9477
  return false
@@ -9262,6 +9504,10 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
9262
9504
  Swal.showValidationMessage("Enter a workspace folder name.")
9263
9505
  return false
9264
9506
  }
9507
+ if (folderName.includes(" ")) {
9508
+ Swal.showValidationMessage("Workspace name cannot contain spaces.")
9509
+ return false
9510
+ }
9265
9511
  try {
9266
9512
  const payload = await createWorkspace(folderName)
9267
9513
  return payload
@@ -9286,23 +9532,8 @@ body.dark .swal2-popup.pinokio-diff-modal .pinokio-modal-footer--commit .pinokio
9286
9532
  return false
9287
9533
  }
9288
9534
  const created = result.value
9289
- addWorkspaceToLocalList({
9290
- name: created.folder || defaultName,
9291
- cwd: created.cwd || ""
9292
- })
9293
- if (sessionSearchInput) {
9294
- sessionSearchInput.value = ""
9295
- }
9296
- setWorkspaceViewMode("sessions", normalizeCwdKey(created.cwd || ""))
9297
- refreshChooserRows()
9298
- void refreshSessionItems({
9299
- sync: true,
9300
- query: "",
9301
- workspaceKey: normalizeCwdKey(created.cwd || "")
9302
- }).then(() => {
9303
- refreshChooserRows()
9304
- })
9305
- return true
9535
+ const finalized = finalizeCreatedWorkspace(created, defaultName)
9536
+ return finalized
9306
9537
  }
9307
9538
 
9308
9539
  const openCreateWorkspaceFromTopAction = async () => {