oysterun 0.1.1-beta.5 → 0.1.1

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.
Files changed (32) hide show
  1. package/dev/client/web/index.html +570 -194
  2. package/dev/client/web-chat/dist/assets/{ReactPrism-B0hQ_03t.js → ReactPrism-D6ngS1Lk.js} +1 -1
  3. package/dev/client/web-chat/dist/assets/{ReactPrism-B0hQ_03t.js.map → ReactPrism-D6ngS1Lk.js.map} +1 -1
  4. package/dev/client/web-chat/dist/assets/{index-BDAb-5Ug.js → index-BU9X1ADi.js} +2 -2
  5. package/dev/client/web-chat/dist/assets/{index-BDAb-5Ug.js.map → index-BU9X1ADi.js.map} +1 -1
  6. package/dev/client/web-chat/dist/assets/{index-O09guDjd.js → index-D9fYeMof.js} +84 -84
  7. package/dev/client/web-chat/dist/assets/index-D9fYeMof.js.map +1 -0
  8. package/dev/client/web-chat/dist/assets/{index-I-iXa4Ju.js → index-DfZLbBnW.js} +1 -1
  9. package/dev/client/web-chat/dist/assets/{index-I-iXa4Ju.js.map → index-DfZLbBnW.js.map} +1 -1
  10. package/dev/client/web-chat/dist/assets/{pdf-DSqRZh0g.js → pdf-DhcicSQp.js} +3 -3
  11. package/dev/client/web-chat/dist/assets/{pdf-DSqRZh0g.js.map → pdf-DhcicSQp.js.map} +1 -1
  12. package/dev/client/web-chat/dist/assets/{web-C3pDgPiA.js → web-DAFRnIAY.js} +1 -1
  13. package/dev/client/web-chat/dist/assets/{web-C3pDgPiA.js.map → web-DAFRnIAY.js.map} +1 -1
  14. package/dev/client/web-chat/dist/index.html +1 -1
  15. package/host-service/adapters/debug-fixture-provider-adapter.mjs +211 -20
  16. package/host-service/agent-config.mjs +8 -14
  17. package/host-service/apns-keys/AuthKey_7QHMWQ8A3P.p8 +6 -0
  18. package/host-service/assets/product-skills/oysterun-host-startup/SKILL.md +4 -0
  19. package/host-service/matrix-facade.mjs +144 -75
  20. package/host-service/portable-scheduler-definitions.mjs +656 -0
  21. package/host-service/restart-restore-state.mjs +217 -0
  22. package/host-service/routec-link-annotation-policy.mjs +29 -4
  23. package/host-service/routec-matrix-storage-adapter.mjs +655 -250
  24. package/host-service/scheduler-service.mjs +277 -76
  25. package/host-service/scheduler-store.mjs +348 -9
  26. package/host-service/server.mjs +870 -63
  27. package/host-service/session-loop-definitions.mjs +107 -0
  28. package/host-service/session-manager.mjs +220 -1
  29. package/host-service/setup.mjs +50 -2
  30. package/host-service/terminal-session-manager.mjs +34 -0
  31. package/package.json +1 -1
  32. package/dev/client/web-chat/dist/assets/index-O09guDjd.js.map +0 -1
@@ -286,6 +286,17 @@
286
286
  color: var(--ink);
287
287
  }
288
288
 
289
+ .browser-titlebar-icon {
290
+ width: 20px;
291
+ height: 20px;
292
+ display: block;
293
+ fill: none;
294
+ stroke: currentColor;
295
+ stroke-width: 2;
296
+ stroke-linecap: round;
297
+ stroke-linejoin: round;
298
+ }
299
+
289
300
  .row,
290
301
  .toolbar,
291
302
  .modal-actions,
@@ -6754,7 +6765,11 @@
6754
6765
 
6755
6766
  function TopBar() {
6756
6767
  return html`
6757
- <header class="topbar" id="topbar">
6768
+ <header
6769
+ class="topbar"
6770
+ id="topbar"
6771
+ data-oysterun-browser-real-topbar-owner="true"
6772
+ >
6758
6773
  <div class="topbar-left">
6759
6774
  <button
6760
6775
  class="icon-btn"
@@ -6774,6 +6789,44 @@
6774
6789
  >
6775
6790
  ←
6776
6791
  </button>
6792
+ <button
6793
+ class="icon-btn hidden"
6794
+ id="browser-site-previous-btn"
6795
+ type="button"
6796
+ aria-label="Previous browser page"
6797
+ title="Previous browser page"
6798
+ data-oysterun-browser-real-topbar-action="previous"
6799
+ data-oysterun-browser-title-action="previous"
6800
+ onClick=${call("goBrowserSitePrevious")}
6801
+ >
6802
+ <svg
6803
+ class="browser-titlebar-icon"
6804
+ aria-hidden="true"
6805
+ focusable="false"
6806
+ viewBox="0 0 24 24"
6807
+ >
6808
+ <path d="M15 18l-6-6 6-6" />
6809
+ </svg>
6810
+ </button>
6811
+ <button
6812
+ class="icon-btn hidden"
6813
+ id="browser-site-next-btn"
6814
+ type="button"
6815
+ aria-label="Next browser page"
6816
+ title="Next browser page"
6817
+ data-oysterun-browser-real-topbar-action="next"
6818
+ data-oysterun-browser-title-action="next"
6819
+ onClick=${call("goBrowserSiteNext")}
6820
+ >
6821
+ <svg
6822
+ class="browser-titlebar-icon"
6823
+ aria-hidden="true"
6824
+ focusable="false"
6825
+ viewBox="0 0 24 24"
6826
+ >
6827
+ <path d="M9 18l6-6-6-6" />
6828
+ </svg>
6829
+ </button>
6777
6830
  <div class="topbar-title" id="topbar-title-block">
6778
6831
  <button
6779
6832
  class="brand-link"
@@ -6986,6 +7039,53 @@
6986
7039
  aria-hidden="true"
6987
7040
  ></span>
6988
7041
  </button>
7042
+ <button
7043
+ class="icon-btn hidden"
7044
+ id="browser-site-share-btn"
7045
+ type="button"
7046
+ aria-label="Copy browser link"
7047
+ title="Copy browser link"
7048
+ data-oysterun-browser-real-topbar-action="share"
7049
+ data-oysterun-browser-title-action="share"
7050
+ data-oysterun-browser-title-share="copy-link"
7051
+ data-oysterun-browser-share-target=""
7052
+ onClick=${call("copyBrowserSurfaceLink")}
7053
+ disabled
7054
+ >
7055
+ <svg
7056
+ class="browser-titlebar-icon"
7057
+ aria-hidden="true"
7058
+ focusable="false"
7059
+ viewBox="0 0 24 24"
7060
+ >
7061
+ <path
7062
+ d="M10 13a5 5 0 0 0 7.07 0l2.12-2.12a5 5 0 0 0-7.07-7.07l-1.21 1.21"
7063
+ />
7064
+ <path
7065
+ d="M14 11a5 5 0 0 0-7.07 0L4.81 13.12a5 5 0 0 0 7.07 7.07l1.21-1.21"
7066
+ />
7067
+ </svg>
7068
+ </button>
7069
+ <button
7070
+ class="icon-btn hidden"
7071
+ id="browser-site-exit-btn"
7072
+ type="button"
7073
+ aria-label="Close browser"
7074
+ title="Close browser"
7075
+ data-oysterun-browser-real-topbar-action="close"
7076
+ data-oysterun-browser-title-action="close"
7077
+ onClick=${call("exitBrowserSurface")}
7078
+ >
7079
+ <svg
7080
+ class="browser-titlebar-icon"
7081
+ aria-hidden="true"
7082
+ focusable="false"
7083
+ viewBox="0 0 24 24"
7084
+ >
7085
+ <path d="M18 6 6 18" />
7086
+ <path d="M6 6l12 12" />
7087
+ </svg>
7088
+ </button>
6989
7089
  </div>
6990
7090
  </header>
6991
7091
  `;
@@ -7254,6 +7354,29 @@
7254
7354
  the running session only.
7255
7355
  </div>
7256
7356
  </div>
7357
+ <div
7358
+ id="create-website-password-row"
7359
+ class="field hidden"
7360
+ data-oysterun-p71-session-setup-web-password="local_config_password_mode"
7361
+ >
7362
+ <label for="create-website-password"
7363
+ >Website Password</label
7364
+ >
7365
+ <input
7366
+ id="create-website-password"
7367
+ type="password"
7368
+ autocomplete="off"
7369
+ data-session-setup-web-local="web.password"
7370
+ />
7371
+ <div
7372
+ id="create-website-password-status"
7373
+ class="field-hint"
7374
+ >
7375
+ Password mode stays locked until this Host-local
7376
+ password is saved to
7377
+ <code>.oysterun/local.json</code>.
7378
+ </div>
7379
+ </div>
7257
7380
  <label
7258
7381
  class="toggle-row"
7259
7382
  data-oysterun-p56postfix1-session-setup-notifications="notifications.enabled"
@@ -7592,7 +7715,9 @@
7592
7715
  <div class="table-title-main">
7593
7716
  <strong>Oysterun Update</strong>
7594
7717
  <div class="table-title-copy">
7595
- Check and update the installed Host package.
7718
+ Check and update the installed Host package. Update
7719
+ & Restart prepares restore state before the service
7720
+ restarts.
7596
7721
  </div>
7597
7722
  </div>
7598
7723
  </div>
@@ -7637,7 +7762,7 @@
7637
7762
  id="host-update-run-btn"
7638
7763
  onClick=${call("runOysterunUpdate")}
7639
7764
  >
7640
- Update now
7765
+ Update & Restart
7641
7766
  </button>
7642
7767
  </div>
7643
7768
  </div>
@@ -7847,24 +7972,6 @@
7847
7972
  ></div>
7848
7973
  </div>
7849
7974
  </div>
7850
- <div class="kv-label">Website Access</div>
7851
- <div class="kv-value">
7852
- <div
7853
- class="value-stack"
7854
- data-oysterun-p56postfix1-host-default-web-access="web.access"
7855
- >
7856
- <select id="host-default-website-access">
7857
- <option value="owner_only">Owner Only</option>
7858
- <option value="password">Password</option>
7859
- <option value="public">Public</option>
7860
- </select>
7861
- <div class="field-hint">
7862
- Fresh Session Setup uses this shared
7863
- <code>web.access</code> default before a folder
7864
- config overrides it.
7865
- </div>
7866
- </div>
7867
- </div>
7868
7975
  <div class="kv-label">Notifications</div>
7869
7976
  <div class="kv-value">
7870
7977
  <label
@@ -8541,68 +8648,9 @@ Terminal output will appear here.</pre
8541
8648
  <div class="page-screen">
8542
8649
  <section
8543
8650
  class="panel browser-surface-panel"
8544
- data-oysterun-browser-title-bar-surface="true"
8651
+ data-oysterun-browser-surface="true"
8545
8652
  style="display:flex; flex-direction:column; min-height:calc(100vh - 120px); overflow:hidden"
8546
8653
  >
8547
- <div
8548
- class="browser-surface-title-bar"
8549
- data-oysterun-browser-title-bar="true"
8550
- style="display:grid; grid-template-columns:auto minmax(0, 1fr) auto; align-items:center; gap:12px; padding-bottom:12px; margin-bottom:12px; border-bottom:1px solid var(--border)"
8551
- >
8552
- <div
8553
- class="browser-surface-title-bar-left"
8554
- data-oysterun-browser-title-bar-side="left"
8555
- style="display:flex; align-items:center; gap:8px"
8556
- >
8557
- <button
8558
- class="btn-soft"
8559
- id="browser-site-previous-btn"
8560
- type="button"
8561
- aria-label="Previous browser page"
8562
- title="Previous browser page"
8563
- data-oysterun-browser-title-action="previous"
8564
- onClick=${call("goBrowserSitePrevious")}
8565
- style="width:40px; height:40px; padding:0; display:inline-flex; align-items:center; justify-content:center"
8566
- >
8567
- <span aria-hidden="true">&lt;</span>
8568
- </button>
8569
- <button
8570
- class="btn-soft"
8571
- id="browser-site-next-btn"
8572
- type="button"
8573
- aria-label="Next browser page"
8574
- title="Next browser page"
8575
- data-oysterun-browser-title-action="next"
8576
- onClick=${call("goBrowserSiteNext")}
8577
- style="width:40px; height:40px; padding:0; display:inline-flex; align-items:center; justify-content:center"
8578
- >
8579
- <span aria-hidden="true">&gt;</span>
8580
- </button>
8581
- </div>
8582
- <div
8583
- class="browser-surface-title-copy"
8584
- data-oysterun-browser-title-bar-center="true"
8585
- style="min-width:0"
8586
- >
8587
- <span class="eyebrow">Website</span>
8588
- <h2 id="browser-site-title" style="margin:0">Website</h2>
8589
- <div class="meta-text" id="browser-site-context">
8590
- Product websites open inside Oysterun.
8591
- </div>
8592
- </div>
8593
- <button
8594
- class="btn-primary"
8595
- id="browser-site-exit-btn"
8596
- type="button"
8597
- aria-label="Close browser"
8598
- title="Close browser"
8599
- data-oysterun-browser-title-action="close"
8600
- onClick=${call("exitBrowserSurface")}
8601
- style="width:40px; height:40px; padding:0; display:inline-flex; align-items:center; justify-content:center"
8602
- >
8603
- <span aria-hidden="true">X</span>
8604
- </button>
8605
- </div>
8606
8654
  <div id="browser-site-error" class="error-box hidden"></div>
8607
8655
  <div
8608
8656
  id="browser-site-unavailable"
@@ -8620,7 +8668,7 @@ Terminal output will appear here.</pre
8620
8668
  </div>
8621
8669
  </div>
8622
8670
  <div
8623
- class="field-hint"
8671
+ class="field-hint hidden"
8624
8672
  id="browser-site-status"
8625
8673
  data-oysterun-browser-status="idle"
8626
8674
  >
@@ -10041,6 +10089,12 @@ Waiting for Claude login output…</pre
10041
10089
  function createHostSchedulerDraft(row = null, overrides = {}) {
10042
10090
  const editableRule = row?.editable_rule || {};
10043
10091
  const editableTarget = row?.editable_target_binding || {};
10092
+ const setupSnapshot =
10093
+ editableTarget.setup_snapshot &&
10094
+ typeof editableTarget.setup_snapshot === "object" &&
10095
+ !Array.isArray(editableTarget.setup_snapshot)
10096
+ ? editableTarget.setup_snapshot
10097
+ : null;
10044
10098
  const weekdaySource = Array.isArray(editableRule.weekdays)
10045
10099
  ? editableRule.weekdays[0] ?? "tuesday"
10046
10100
  : editableRule.weekday || "tuesday";
@@ -10057,22 +10111,30 @@ Waiting for Claude login output…</pre
10057
10111
  monthDay: Number(editableRule.day_of_month || editableRule.day || 1),
10058
10112
  onceAt: editableRule.at || "",
10059
10113
  timezone: editableRule.timezone || getHostSchedulerTimezone(),
10060
- targetKind: editableTarget.kind || "session_setup_record",
10114
+ targetKind: editableTarget.kind || "setup_snapshot",
10061
10115
  hostSessionId: editableTarget.host_session_id || "",
10062
10116
  savedSessionId: editableTarget.saved_session_id || "",
10063
10117
  sessionSetupRecordId: editableTarget.session_setup_record_id || "",
10064
10118
  agentId: editableTarget.agent_id || "",
10065
- sessionSetupPayload: editableTarget.session_setup_payload || null,
10119
+ sessionSetupPayload:
10120
+ setupSnapshot || editableTarget.session_setup_payload || null,
10066
10121
  provider:
10122
+ setupSnapshot?.provider ||
10067
10123
  editableTarget.session_setup_payload?.provider ||
10068
10124
  editableTarget.session_setup_fields?.provider ||
10069
10125
  "",
10070
10126
  model:
10127
+ setupSnapshot?.model ||
10071
10128
  editableTarget.session_setup_payload?.model ||
10072
10129
  editableTarget.session_setup_fields?.model ||
10073
10130
  "",
10074
- agentFolder: editableTarget.session_setup_fields?.agent_folder || "",
10131
+ agentFolder:
10132
+ setupSnapshot?.agent_folder ||
10133
+ editableTarget.session_setup_fields?.agent_folder ||
10134
+ "",
10075
10135
  permissionMode:
10136
+ setupSnapshot?.permission_mode ||
10137
+ setupSnapshot?.approval_policy ||
10076
10138
  editableTarget.session_setup_payload?.permission_mode ||
10077
10139
  editableTarget.session_setup_payload?.approval_policy ||
10078
10140
  editableTarget.session_setup_fields?.permission_mode ||
@@ -11237,6 +11299,8 @@ Waiting for Claude login output…</pre
11237
11299
  returnPath: "",
11238
11300
  routeTarget: "",
11239
11301
  currentTarget: "",
11302
+ launchUrl: "",
11303
+ launchTarget: "",
11240
11304
  history: [],
11241
11305
  index: -1,
11242
11306
  historyNavigation: false,
@@ -11321,6 +11385,7 @@ Waiting for Claude login output…</pre
11321
11385
  loginQrError: "",
11322
11386
  restartLoading: false,
11323
11387
  restartStatus: "",
11388
+ restartRestore: null,
11324
11389
  update: null,
11325
11390
  updateChecking: false,
11326
11391
  updateRunning: false,
@@ -11378,6 +11443,7 @@ Waiting for Claude login output…</pre
11378
11443
  let terminalViewState = createTerminalViewState();
11379
11444
  let terminalPollTimer = null;
11380
11445
  let browserSurfaceState = createBrowserSurfaceState();
11446
+ let browserSurfaceLaunchRequestId = 0;
11381
11447
  let hostPreferencesState = createHostPreferencesState();
11382
11448
  function getRequestedHostDefaultProvider() {
11383
11449
  const selectedProvider =
@@ -11406,10 +11472,7 @@ Waiting for Claude login output…</pre
11406
11472
  $("host-notification-web-sound-toggle")?.checked !== false,
11407
11473
  notificationSoundAppEnabled:
11408
11474
  $("host-notification-app-sound-toggle")?.checked !== false,
11409
- defaultWebsiteAccess: normalizeAgentWebAccess(
11410
- $("host-default-website-access")?.value ||
11411
- hostPreferencesState.defaultWebsiteAccess
11412
- ),
11475
+ defaultWebsiteAccess: hostPreferencesState.defaultWebsiteAccess,
11413
11476
  defaultNotificationsEnabled:
11414
11477
  $("host-default-notifications-enabled")?.checked !== false,
11415
11478
  defaultRuntimeCapabilities: readHostDefaultRuntimeCapabilityState(),
@@ -21064,6 +21127,9 @@ Waiting for Claude login output…</pre
21064
21127
  const website = getLiveSessionWebsite(liveSession);
21065
21128
  const access = normalizeAgentWebAccess(website?.access);
21066
21129
  const root = sanitizeRouteText(website?.root);
21130
+ const passwordConfigured = website?.password_configured === true;
21131
+ const passwordModeLocked =
21132
+ access === "password" && passwordConfigured !== true;
21067
21133
  const saving =
21068
21134
  agentProfileWebsiteAccessState.sessionId === liveSession?.sessionId &&
21069
21135
  agentProfileWebsiteAccessState.saving === true;
@@ -21086,13 +21152,43 @@ Waiting for Claude login output…</pre
21086
21152
  <select
21087
21153
  id="agent-profile-website-access"
21088
21154
  data-session-profile-web-access="web.access"
21155
+ onchange="window.syncAgentProfileWebsitePasswordMode && window.syncAgentProfileWebsitePasswordMode()"
21089
21156
  ${saving ? "disabled" : ""}
21090
21157
  >${renderAgentWebAccessOptions(access)}</select>
21091
21158
  </label>
21159
+ <label
21160
+ id="agent-profile-website-password-row"
21161
+ class="field compact-field ${
21162
+ access === "password" ? "" : "hidden"
21163
+ }"
21164
+ for="agent-profile-website-password"
21165
+ data-oysterun-p71-session-profile-web-password="local_config_password_mode"
21166
+ aria-hidden="${access === "password" ? "false" : "true"}"
21167
+ >
21168
+ <span>Website Password</span>
21169
+ <input
21170
+ id="agent-profile-website-password"
21171
+ type="password"
21172
+ autocomplete="off"
21173
+ placeholder="${passwordConfigured ? "Configured" : ""}"
21174
+ data-session-profile-web-local="web.password"
21175
+ ${access === "password" && !saving ? "" : "disabled"}
21176
+ />
21177
+ <div
21178
+ id="agent-profile-website-password-status"
21179
+ class="field-hint"
21180
+ >${esc(
21181
+ passwordModeLocked
21182
+ ? "Locked: password mode needs a Host-local password saved in .oysterun/local.json."
21183
+ : passwordConfigured
21184
+ ? "A Host-local password is configured. Enter a new value only to replace it."
21185
+ : "Shown only for password mode."
21186
+ )}</div>
21187
+ </label>
21092
21188
  <label class="toggle-row" data-session-profile-update-config-row="web.access">
21093
21189
  <span>
21094
21190
  <strong>Update .oysterun/config.json</strong>
21095
- <div class="field-hint">Unchecked saves only this running session. Checked also persists shared web.access.</div>
21191
+ <div class="field-hint">Unchecked saves only this running session. Checked persists shared web.access and any entered Host-local website password.</div>
21096
21192
  </span>
21097
21193
  <input
21098
21194
  id="agent-profile-website-update-config"
@@ -21137,6 +21233,23 @@ Waiting for Claude login output…</pre
21137
21233
  );
21138
21234
  }
21139
21235
 
21236
+ function syncAgentProfileWebsitePasswordMode() {
21237
+ const access = normalizeAgentWebAccess(
21238
+ $("agent-profile-website-access")?.value
21239
+ );
21240
+ const row = $("agent-profile-website-password-row");
21241
+ const input = $("agent-profile-website-password");
21242
+ const passwordMode = access === "password";
21243
+ if (row) {
21244
+ row.classList.toggle("hidden", !passwordMode);
21245
+ row.setAttribute("aria-hidden", passwordMode ? "false" : "true");
21246
+ }
21247
+ if (input) {
21248
+ input.disabled = !passwordMode;
21249
+ if (!passwordMode) input.value = "";
21250
+ }
21251
+ }
21252
+
21140
21253
  async function saveAgentProfileWebsiteAccess() {
21141
21254
  const liveSession = getSelectedLiveSession();
21142
21255
  if (!liveSession?.sessionId) return;
@@ -21146,12 +21259,20 @@ Waiting for Claude login output…</pre
21146
21259
  const updateConfig =
21147
21260
  $("agent-profile-website-update-config")?.checked === true;
21148
21261
  const nextAccess = normalizeAgentWebAccess(select?.value);
21262
+ const nextWebsitePassword =
21263
+ nextAccess === "password" && updateConfig === true
21264
+ ? String($("agent-profile-website-password")?.value || "").trim()
21265
+ : "";
21149
21266
  const previousWebsite = getLiveSessionWebsite(liveSession);
21150
21267
  const optimisticWebsite = {
21151
21268
  ...(previousWebsite || {}),
21152
21269
  access: nextAccess,
21153
21270
  current_session_only: updateConfig !== true,
21154
21271
  live_session_access_override: updateConfig !== true,
21272
+ password_configured:
21273
+ nextWebsitePassword || previousWebsite?.password_configured === true
21274
+ ? true
21275
+ : previousWebsite?.password_configured === true,
21155
21276
  };
21156
21277
  agentProfileWebsiteAccessState = {
21157
21278
  ...agentProfileWebsiteAccessState,
@@ -21170,6 +21291,9 @@ Waiting for Claude login output…</pre
21170
21291
  {
21171
21292
  session_id: liveSession.sessionId,
21172
21293
  web_access: nextAccess,
21294
+ ...(nextWebsitePassword
21295
+ ? { web_password: nextWebsitePassword }
21296
+ : {}),
21173
21297
  update_config: updateConfig,
21174
21298
  }
21175
21299
  );
@@ -23029,7 +23153,7 @@ Waiting for Claude login output…</pre
23029
23153
  if (screen === "browser") {
23030
23154
  return {
23031
23155
  title: "Browser",
23032
- subtitle: "View Host websites inside Oysterun.",
23156
+ subtitle: "",
23033
23157
  };
23034
23158
  }
23035
23159
  return { title: "Oysterun", subtitle: "" };
@@ -23040,6 +23164,7 @@ Waiting for Claude login output…</pre
23040
23164
  const root = isRootScreen();
23041
23165
  const isChat = screen === "chat";
23042
23166
  const isAgentProfile = screen === "agent-profile";
23167
+ const isBrowser = screen === "browser";
23043
23168
  const currentRoute = normalizeClientRoute(getCurrentRoute());
23044
23169
  const isExplorer = screen === "explorer";
23045
23170
  const isSchedulerList =
@@ -23055,8 +23180,18 @@ Waiting for Claude login output…</pre
23055
23180
  const activeRouteSessionId = getActiveSessionId();
23056
23181
  const hasSelectedLiveSession = Boolean(selectedLiveSession);
23057
23182
  const hasActiveRouteSession = Boolean(activeRouteSessionId);
23058
- setVisible("topbar-menu-btn", root && !isSchedulerSubpage);
23059
- setVisible("topbar-back-btn", !root || isSchedulerSubpage);
23183
+ setVisible(
23184
+ "topbar-menu-btn",
23185
+ root && !isSchedulerSubpage && !isBrowser
23186
+ );
23187
+ setVisible(
23188
+ "topbar-back-btn",
23189
+ (!root || isSchedulerSubpage) && !isBrowser
23190
+ );
23191
+ setVisible("browser-site-previous-btn", isBrowser);
23192
+ setVisible("browser-site-next-btn", isBrowser);
23193
+ setVisible("browser-site-share-btn", isBrowser);
23194
+ setVisible("browser-site-exit-btn", isBrowser);
23060
23195
  setVisible("entry-start-session-btn", screen === "sessions");
23061
23196
  setVisible("topbar-scheduler-new-btn", isSchedulerList);
23062
23197
  setVisible("topbar-session-loop-new-btn", isSessionLoop);
@@ -23156,7 +23291,10 @@ Waiting for Claude login output…</pre
23156
23291
  !isChat &&
23157
23292
  !isAgentProfile
23158
23293
  );
23159
- setVisible("topbar-chat-center", isChat || isAgentProfile);
23294
+ setVisible(
23295
+ "topbar-chat-center",
23296
+ (isChat || isAgentProfile) && !isBrowser
23297
+ );
23160
23298
  setVisible("stream-state", isChat);
23161
23299
  setVisible(
23162
23300
  "btn-agent-site",
@@ -23222,11 +23360,13 @@ Waiting for Claude login output…</pre
23222
23360
  );
23223
23361
  setText("topbar-page-title", meta.title);
23224
23362
  setText("topbar-page-subtitle", meta.subtitle);
23363
+ setVisible("topbar-page-subtitle", Boolean(meta.subtitle));
23225
23364
  $("topbar")?.classList.toggle("chat-mode", isChat);
23226
23365
  $("topbar")?.classList.toggle("sessions-mode", screen === "sessions");
23227
23366
  $("topbar")?.classList.toggle("scheduler-mode", isSchedulerList);
23228
23367
  $("topbar")?.classList.toggle("explorer-mode", isExplorer);
23229
23368
  $("topbar")?.classList.toggle("session-loop-mode", isSessionLoop);
23369
+ $("topbar")?.classList.toggle("browser-mode", isBrowser);
23230
23370
  $("topbar")?.classList.toggle(
23231
23371
  "scheduler-setup-mode",
23232
23372
  isSchedulerSubpage
@@ -25207,12 +25347,39 @@ Waiting for Claude login output…</pre
25207
25347
  showBanner("Website entry path is unavailable.", true);
25208
25348
  return;
25209
25349
  }
25210
- void navigateToRoute(
25211
- buildBrowserRoute({
25212
- target,
25213
- source: "web",
25214
- returnPath: serializeClientRoute(buildWebRoute()),
25215
- })
25350
+ openWebsiteTargetByRuntime({
25351
+ target,
25352
+ source: "web",
25353
+ returnPath: serializeClientRoute(buildWebRoute()),
25354
+ });
25355
+ }
25356
+
25357
+ function openWebsiteTargetByRuntime({
25358
+ target = "",
25359
+ sessionId = "",
25360
+ source = "",
25361
+ returnPath = "",
25362
+ } = {}) {
25363
+ const normalizedTarget = normalizeBrowserSiteTarget(target);
25364
+ if (!normalizedTarget) {
25365
+ showToast("Website target is unavailable.", true, 3200);
25366
+ return;
25367
+ }
25368
+ if (isOysterunCapacitorIOSRuntime()) {
25369
+ void navigateToRoute(
25370
+ buildBrowserRoute({
25371
+ target: normalizedTarget,
25372
+ sessionId,
25373
+ source,
25374
+ returnPath,
25375
+ })
25376
+ );
25377
+ return;
25378
+ }
25379
+ window.open(
25380
+ new URL(normalizedTarget, BASE).toString(),
25381
+ "_blank",
25382
+ "noopener,noreferrer"
25216
25383
  );
25217
25384
  }
25218
25385
 
@@ -25509,7 +25676,11 @@ Waiting for Claude login output…</pre
25509
25676
  function getInteractiveRichHrefRouteSurface(rawHref) {
25510
25677
  const parsed = parseInteractiveRichHref(rawHref);
25511
25678
  if (!parsed) return "";
25512
- if (parsed.kind === "host-site") return "app_browser";
25679
+ if (parsed.kind === "host-site") {
25680
+ return isOysterunCapacitorIOSRuntime()
25681
+ ? "app_browser"
25682
+ : "browser_new_tab";
25683
+ }
25513
25684
  if (parsed.kind === "file-preview") return "file_preview";
25514
25685
  if (parsed.kind === "explorer") return "explorer";
25515
25686
  if (parsed.kind === "unsupported-local-path") return "unsupported";
@@ -26576,6 +26747,10 @@ Waiting for Claude login output…</pre
26576
26747
  loginQrLoading: false,
26577
26748
  loginQrError: "",
26578
26749
  restartLoading: false,
26750
+ restartRestore:
26751
+ data.restart_restore && typeof data.restart_restore === "object"
26752
+ ? data.restart_restore
26753
+ : hostPreferencesState.restartRestore,
26579
26754
  update:
26580
26755
  data.update && typeof data.update === "object"
26581
26756
  ? data.update
@@ -28000,15 +28175,6 @@ Waiting for Claude login output…</pre
28000
28175
  if ($("host-default-provider")) {
28001
28176
  renderHostDefaultProviderOptions();
28002
28177
  }
28003
- const hostDefaultWebsiteAccess = $("host-default-website-access");
28004
- if (
28005
- hostDefaultWebsiteAccess &&
28006
- document.activeElement !== hostDefaultWebsiteAccess
28007
- ) {
28008
- hostDefaultWebsiteAccess.value = normalizeAgentWebAccess(
28009
- hostPreferencesState.defaultWebsiteAccess
28010
- );
28011
- }
28012
28178
  const hostDefaultNotifications = $(
28013
28179
  "host-default-notifications-enabled"
28014
28180
  );
@@ -28163,9 +28329,9 @@ Waiting for Claude login output…</pre
28163
28329
  disabled ||
28164
28330
  hostPreferencesState.restartLoading ||
28165
28331
  !hostPreferencesState.hostRestartSupported ||
28166
- !hostPreferencesState.directHostUrlRestartRequired;
28332
+ hasDirectAccessDraftChanges;
28167
28333
  restartButton.textContent = hostPreferencesState.restartLoading
28168
- ? "Restarting..."
28334
+ ? "Preparing restore..."
28169
28335
  : "Restart Host";
28170
28336
  }
28171
28337
  const update = hostPreferencesState.update || {};
@@ -28184,7 +28350,7 @@ Waiting for Claude login output…</pre
28184
28350
  updateStatus = "Oysterun is up to date for this channel.";
28185
28351
  } else if (update.update_supported === false) {
28186
28352
  updateStatus =
28187
- "Update now is only available on the production npm Host stack.";
28353
+ "Update & Restart is only available on the production npm Host stack.";
28188
28354
  } else {
28189
28355
  updateStatus = "Click Check for updates to query npm.";
28190
28356
  }
@@ -28209,8 +28375,8 @@ Waiting for Claude login output…</pre
28209
28375
  update.update_supported !== true ||
28210
28376
  update.update_available !== true;
28211
28377
  updateRunButton.textContent = hostPreferencesState.updateRunning
28212
- ? "Updating..."
28213
- : "Update now";
28378
+ ? "Preparing restore..."
28379
+ : "Update & Restart";
28214
28380
  }
28215
28381
  const qrPanel = $("host-login-qr-panel");
28216
28382
  const qr = hostPreferencesState.loginQr;
@@ -28279,7 +28445,6 @@ Waiting for Claude login output…</pre
28279
28445
  "disabled",
28280
28446
  defaultProviderDisabled
28281
28447
  );
28282
- $("host-default-website-access")?.toggleAttribute("disabled", disabled);
28283
28448
  $("host-default-notifications-enabled")?.toggleAttribute(
28284
28449
  "disabled",
28285
28450
  disabled
@@ -28385,11 +28550,28 @@ Waiting for Claude login output…</pre
28385
28550
 
28386
28551
  async function restartHostFromPreferences() {
28387
28552
  if (hostPreferencesState.restartLoading) return;
28388
- if (!hostPreferencesState.directHostUrlRestartRequired) return;
28553
+ if (
28554
+ hostPreferencesState.loading ||
28555
+ hostPreferencesState.saving ||
28556
+ !hostPreferencesState.hostRestartSupported
28557
+ ) {
28558
+ return;
28559
+ }
28560
+ if (hasUnsavedHostPreferencesChanges()) {
28561
+ hostPreferencesState = {
28562
+ ...hostPreferencesState,
28563
+ restartStatus:
28564
+ "Save Host Preferences changes before restarting Host.",
28565
+ loginQrError: "",
28566
+ };
28567
+ renderHostPreferences();
28568
+ return;
28569
+ }
28389
28570
  hostPreferencesState = {
28390
28571
  ...hostPreferencesState,
28391
28572
  restartLoading: true,
28392
- restartStatus: "Scheduling Host restart...",
28573
+ restartStatus:
28574
+ "Preparing restore state for existing sessions and enabled loops...",
28393
28575
  loginQrError: "",
28394
28576
  };
28395
28577
  renderHostPreferences();
@@ -28400,8 +28582,10 @@ Waiting for Claude login output…</pre
28400
28582
  hostPreferencesState = {
28401
28583
  ...hostPreferencesState,
28402
28584
  restartLoading: false,
28585
+ restartRestore:
28586
+ data.restart_restore || hostPreferencesState.restartRestore,
28403
28587
  restartStatus:
28404
- "Host restart scheduled. Reconnect after the service comes back.",
28588
+ "Host restart scheduled. Existing sessions and enabled loops will be restored after reconnect; active work may show interrupted status.",
28405
28589
  };
28406
28590
  renderHostPreferences();
28407
28591
  if (redirectUrl) {
@@ -28482,13 +28666,14 @@ Waiting for Claude login output…</pre
28482
28666
  return;
28483
28667
  }
28484
28668
  const confirmed = window.confirm(
28485
- "Updating Oysterun restarts the Host. Existing live runtime sessions and running loops will close; chat history remains and supported sessions can be resumed from Sessions after restart. Continue?"
28669
+ "Updating Oysterun restarts the Host. Oysterun will prepare restore state for existing sessions and enabled in-session loops; active provider, terminal, loop, or scheduler work may be marked interrupted and will not be replayed. Continue?"
28486
28670
  );
28487
28671
  if (!confirmed) return;
28488
28672
  hostPreferencesState = {
28489
28673
  ...hostPreferencesState,
28490
28674
  updateRunning: true,
28491
- updateStatus: "Scheduling Oysterun update...",
28675
+ updateStatus:
28676
+ "Resolving exact package version and preparing restore state...",
28492
28677
  updateError: "",
28493
28678
  };
28494
28679
  renderHostPreferences();
@@ -28499,9 +28684,11 @@ Waiting for Claude login output…</pre
28499
28684
  hostPreferencesState = {
28500
28685
  ...hostPreferencesState,
28501
28686
  update: data,
28687
+ restartRestore:
28688
+ data.restart_restore || hostPreferencesState.restartRestore,
28502
28689
  updateRunning: false,
28503
28690
  updateStatus:
28504
- "Oysterun update scheduled. Reconnect after the service comes back.",
28691
+ "Oysterun update and restore-aware restart scheduled. Reconnect after the service comes back.",
28505
28692
  updateError: "",
28506
28693
  };
28507
28694
  renderHostPreferences();
@@ -28565,9 +28752,6 @@ Waiting for Claude login output…</pre
28565
28752
  ? codexCommandInput.value.trim()
28566
28753
  : "";
28567
28754
  const nextDefaultProvider = getRequestedHostDefaultProvider();
28568
- const nextDefaultWebsiteAccess = normalizeAgentWebAccess(
28569
- $("host-default-website-access")?.value
28570
- );
28571
28755
  const nextDefaultNotificationsEnabled =
28572
28756
  $("host-default-notifications-enabled")?.checked !== false;
28573
28757
  const nextDefaultRuntimeCapabilities =
@@ -28577,9 +28761,6 @@ Waiting for Claude login output…</pre
28577
28761
  interface_type: normalizeFirstPartyInterfaceType(
28578
28762
  HOST_DEFAULT_SESSION_DEFAULTS.interfaceType
28579
28763
  ),
28580
- web: {
28581
- access: nextDefaultWebsiteAccess,
28582
- },
28583
28764
  notifications: {
28584
28765
  enabled: nextDefaultNotificationsEnabled,
28585
28766
  },
@@ -28616,7 +28797,6 @@ Waiting for Claude login output…</pre
28616
28797
  publicBaseUrl: requestedPublicBaseUrl,
28617
28798
  defaultBrowsePath: requestedPath,
28618
28799
  defaultProvider: nextDefaultProvider,
28619
- defaultWebsiteAccess: nextDefaultWebsiteAccess,
28620
28800
  defaultNotificationsEnabled: nextDefaultNotificationsEnabled,
28621
28801
  defaultRuntimeCapabilities: nextDefaultRuntimeCapabilities,
28622
28802
  effectiveDefaultProvider:
@@ -29231,12 +29411,43 @@ Waiting for Claude login output…</pre
29231
29411
  const select = $("create-website-access");
29232
29412
  if (!select) return;
29233
29413
  select.value = normalizeAgentWebAccess(value);
29414
+ syncCreateWebsitePasswordMode();
29234
29415
  }
29235
29416
 
29236
29417
  function getCreateWebsiteAccessValue() {
29237
29418
  return normalizeAgentWebAccess($("create-website-access")?.value);
29238
29419
  }
29239
29420
 
29421
+ function getCreateWebsitePasswordValue() {
29422
+ return String($("create-website-password")?.value || "").trim();
29423
+ }
29424
+
29425
+ function setCreateWebsitePasswordValue(value = "") {
29426
+ const input = $("create-website-password");
29427
+ if (!input) return;
29428
+ input.value = String(value || "");
29429
+ }
29430
+
29431
+ function syncCreateWebsitePasswordMode() {
29432
+ const row = $("create-website-password-row");
29433
+ const input = $("create-website-password");
29434
+ const status = $("create-website-password-status");
29435
+ const passwordMode = getCreateWebsiteAccessValue() === "password";
29436
+ if (row) {
29437
+ row.classList.toggle("hidden", !passwordMode);
29438
+ row.setAttribute("aria-hidden", passwordMode ? "false" : "true");
29439
+ }
29440
+ if (input) {
29441
+ input.disabled = !passwordMode;
29442
+ if (!passwordMode) input.value = "";
29443
+ }
29444
+ if (status) {
29445
+ status.textContent = passwordMode
29446
+ ? "Password mode stays locked until this Host-local password is saved to .oysterun/local.json."
29447
+ : "";
29448
+ }
29449
+ }
29450
+
29240
29451
  function setCreateNotificationsEnabledValue(value) {
29241
29452
  const input = $("create-notifications-enabled");
29242
29453
  if (!input) return;
@@ -29840,6 +30051,8 @@ Waiting for Claude login output…</pre
29840
30051
  returnPath: normalizeBrowserReturnPath(returnPath),
29841
30052
  routeTarget: normalizedTarget,
29842
30053
  currentTarget: normalizedTarget,
30054
+ launchUrl: "",
30055
+ launchTarget: "",
29843
30056
  history: normalizedTarget ? [normalizedTarget] : [],
29844
30057
  index: normalizedTarget ? 0 : -1,
29845
30058
  loading: Boolean(normalizedTarget),
@@ -29864,7 +30077,49 @@ Waiting for Claude login output…</pre
29864
30077
  }
29865
30078
  }
29866
30079
 
29867
- function syncBrowserSurfaceFrame(force = false) {
30080
+ async function createBrowserSurfaceHandoffLaunch(target) {
30081
+ const normalizedTarget = normalizeBrowserSiteTarget(target);
30082
+ if (!normalizedTarget) {
30083
+ throw new Error(
30084
+ "Only /sites/<agent_id>/ targets open in this surface."
30085
+ );
30086
+ }
30087
+ const data = await apiWithAuthRetry("POST", "/api/browser/handoff", {
30088
+ target: normalizedTarget,
30089
+ });
30090
+ const rawLaunchUrl =
30091
+ typeof data?.launch_url === "string" ? data.launch_url.trim() : "";
30092
+ if (!rawLaunchUrl) {
30093
+ throw new Error("Browser handoff response missing launch_url.");
30094
+ }
30095
+ const launchUrl = new URL(rawLaunchUrl, BASE);
30096
+ if (
30097
+ launchUrl.origin !== BASE ||
30098
+ !launchUrl.pathname.startsWith("/browser-handoff/")
30099
+ ) {
30100
+ throw new Error(
30101
+ "Browser handoff response returned an invalid launch_url."
30102
+ );
30103
+ }
30104
+ const normalizedTargetPath =
30105
+ normalizeBrowserSiteTarget(data?.target_path || normalizedTarget) ||
30106
+ normalizedTarget;
30107
+ return {
30108
+ launchUrl: `${launchUrl.pathname}${launchUrl.search}${launchUrl.hash}`,
30109
+ targetPath: normalizedTargetPath,
30110
+ };
30111
+ }
30112
+
30113
+ function getBrowserSurfaceHandoffErrorMessage(err) {
30114
+ if (isUnauthorizedError(err)) {
30115
+ return "Website unavailable. Browser handoff requires Host dashboard authentication.";
30116
+ }
30117
+ return err?.message
30118
+ ? `Website unavailable. Browser handoff failed: ${err.message}`
30119
+ : "Website unavailable. Browser handoff failed.";
30120
+ }
30121
+
30122
+ async function syncBrowserSurfaceFrame(force = false) {
29868
30123
  const frame = $("browser-site-frame");
29869
30124
  if (!frame) return;
29870
30125
  const target = browserSurfaceState.currentTarget || "";
@@ -29875,17 +30130,67 @@ Waiting for Claude login output…</pre
29875
30130
  }
29876
30131
  return;
29877
30132
  }
29878
- const frameAttributeTarget = normalizeBrowserSiteTarget(
29879
- frame.getAttribute("src") || ""
29880
- );
29881
30133
  const frameLocationTarget = getBrowserSurfaceFrameLocationTarget(frame);
30134
+ if (!force && frameLocationTarget === target) return;
30135
+
30136
+ let launchUrl = browserSurfaceState.launchUrl || "";
29882
30137
  if (
29883
30138
  force ||
29884
- frameAttributeTarget !== target ||
29885
- frameLocationTarget !== target
30139
+ !launchUrl ||
30140
+ browserSurfaceState.launchTarget !== target
29886
30141
  ) {
29887
- frame.setAttribute("data-oysterun-browser-expected-target", target);
29888
- frame.src = new URL(target, BASE).href;
30142
+ const requestId = ++browserSurfaceLaunchRequestId;
30143
+ browserSurfaceState = {
30144
+ ...browserSurfaceState,
30145
+ loading: true,
30146
+ unavailable: false,
30147
+ error: "",
30148
+ status: "Opening website...",
30149
+ };
30150
+ renderBrowserSurfaceView();
30151
+ let handoff;
30152
+ try {
30153
+ handoff = await createBrowserSurfaceHandoffLaunch(target);
30154
+ } catch (err) {
30155
+ if (requestId !== browserSurfaceLaunchRequestId) return;
30156
+ markBrowserSurfaceUnavailable(
30157
+ target,
30158
+ getBrowserSurfaceHandoffErrorMessage(err)
30159
+ );
30160
+ renderBrowserSurfaceView();
30161
+ return;
30162
+ }
30163
+ if (
30164
+ requestId !== browserSurfaceLaunchRequestId ||
30165
+ browserSurfaceState.currentTarget !== target
30166
+ ) {
30167
+ return;
30168
+ }
30169
+ launchUrl = handoff.launchUrl;
30170
+ browserSurfaceState = {
30171
+ ...browserSurfaceState,
30172
+ currentTarget: handoff.targetPath,
30173
+ launchUrl: handoff.launchUrl,
30174
+ launchTarget: handoff.targetPath,
30175
+ loading: true,
30176
+ unavailable: false,
30177
+ error: "",
30178
+ status: "Opening website...",
30179
+ };
30180
+ renderBrowserSurfaceView();
30181
+ }
30182
+
30183
+ const expectedSrc = new URL(launchUrl, BASE).href;
30184
+ if (force || frame.getAttribute("src") !== expectedSrc) {
30185
+ frame.setAttribute(
30186
+ "data-oysterun-browser-expected-target",
30187
+ browserSurfaceState.currentTarget || target
30188
+ );
30189
+ frame.setAttribute(
30190
+ "data-oysterun-browser-launch-surface",
30191
+ "browser_handoff"
30192
+ );
30193
+ frame.src = expectedSrc;
29889
30194
  }
29890
30195
  }
29891
30196
 
@@ -29910,11 +30215,35 @@ Waiting for Claude login output…</pre
29910
30215
  return "ready";
29911
30216
  }
29912
30217
 
30218
+ function getBrowserSurfaceShareTarget() {
30219
+ if (browserSurfaceState.loading || browserSurfaceState.unavailable) {
30220
+ return "";
30221
+ }
30222
+ return normalizeBrowserSiteTarget(
30223
+ browserSurfaceState.currentTarget || browserSurfaceState.routeTarget
30224
+ );
30225
+ }
30226
+
30227
+ async function copyBrowserSurfaceLink() {
30228
+ const target = getBrowserSurfaceShareTarget();
30229
+ if (!target) {
30230
+ showToast("Website link is unavailable.", true, 3200);
30231
+ return;
30232
+ }
30233
+ try {
30234
+ await writeTextToClipboard(new URL(target, BASE).toString());
30235
+ showToast("Copied browser link.");
30236
+ } catch (err) {
30237
+ showToast(err.message || "Could not copy browser link.", true, 4200);
30238
+ }
30239
+ }
30240
+
29913
30241
  function renderBrowserSurfaceView() {
29914
30242
  const previousBtn = $("browser-site-previous-btn");
29915
30243
  const nextBtn = $("browser-site-next-btn");
30244
+ const shareBtn = $("browser-site-share-btn");
29916
30245
  const frame = $("browser-site-frame");
29917
- const target = browserSurfaceState.currentTarget || "";
30246
+ const shareTarget = getBrowserSurfaceShareTarget();
29918
30247
  const stateLabel = getBrowserSurfaceStateLabel();
29919
30248
  if (previousBtn) {
29920
30249
  previousBtn.disabled =
@@ -29929,20 +30258,16 @@ Waiting for Claude login output…</pre
29929
30258
  browserSurfaceState.index < 0 ||
29930
30259
  browserSurfaceState.index >= browserSurfaceState.history.length - 1;
29931
30260
  }
30261
+ if (shareBtn) {
30262
+ shareBtn.disabled = !shareTarget;
30263
+ shareBtn.setAttribute(
30264
+ "data-oysterun-browser-share-target",
30265
+ shareTarget
30266
+ );
30267
+ }
29932
30268
  if (frame) {
29933
30269
  frame.classList.toggle("hidden", browserSurfaceState.unavailable);
29934
30270
  }
29935
- setText("browser-site-title", target || "Website unavailable");
29936
- setText(
29937
- "browser-site-context",
29938
- browserSurfaceState.source === "chat" || browserSurfaceState.sessionId
29939
- ? "Opened from Chat. Close returns to the source chat."
29940
- : browserSurfaceState.source === "mail"
29941
- ? "Opened from Mail. Close returns to the source Mail item."
29942
- : browserSurfaceState.source === "preview"
29943
- ? "Opened from Preview. Close returns to the source preview."
29944
- : "Opened from Web. Close returns to the Web tab."
29945
- );
29946
30271
  setText(
29947
30272
  "browser-site-status",
29948
30273
  browserSurfaceState.status || stateLabel
@@ -29984,6 +30309,8 @@ Waiting for Claude login output…</pre
29984
30309
  currentTarget: normalizedTarget || browserSurfaceState.currentTarget,
29985
30310
  loading: false,
29986
30311
  unavailable: true,
30312
+ launchUrl: "",
30313
+ launchTarget: "",
29987
30314
  historyNavigation: false,
29988
30315
  error:
29989
30316
  message ||
@@ -30055,6 +30382,8 @@ Waiting for Claude login output…</pre
30055
30382
  currentTarget: normalizedTarget,
30056
30383
  history,
30057
30384
  index,
30385
+ launchUrl: "",
30386
+ launchTarget: "",
30058
30387
  loading: false,
30059
30388
  unavailable: false,
30060
30389
  historyNavigation: false,
@@ -31630,8 +31959,14 @@ Waiting for Claude login output…</pre
31630
31959
  function getHostSchedulerSourceSetupSnapshot(row) {
31631
31960
  const target =
31632
31961
  row?.editable_target_binding || row?.target_binding || {};
31962
+ const setupSnapshot =
31963
+ target.setup_snapshot &&
31964
+ typeof target.setup_snapshot === "object" &&
31965
+ !Array.isArray(target.setup_snapshot)
31966
+ ? target.setup_snapshot
31967
+ : null;
31633
31968
  const payload = sanitizeSchedulerSessionSetupPayload(
31634
- target.session_setup_payload
31969
+ setupSnapshot || target.session_setup_payload
31635
31970
  );
31636
31971
  const fields =
31637
31972
  target.session_setup_fields &&
@@ -31640,7 +31975,11 @@ Waiting for Claude login output…</pre
31640
31975
  ? target.session_setup_fields
31641
31976
  : {};
31642
31977
  const provider =
31643
- payload.provider || fields.provider || target.provider || "";
31978
+ setupSnapshot?.provider ||
31979
+ payload.provider ||
31980
+ fields.provider ||
31981
+ target.provider ||
31982
+ "";
31644
31983
  const model = payload.model || fields.model || target.model || "";
31645
31984
  const permissionMode =
31646
31985
  payload.permission_mode ||
@@ -31648,10 +31987,14 @@ Waiting for Claude login output…</pre
31648
31987
  fields.permission_mode ||
31649
31988
  fields.approval_policy ||
31650
31989
  "";
31651
- const agentFolder = fields.agent_folder || target.agent_folder || "";
31990
+ const agentFolder =
31991
+ setupSnapshot?.agent_folder ||
31992
+ fields.agent_folder ||
31993
+ target.agent_folder ||
31994
+ "";
31652
31995
  const agentId = target.agent_id || row?.agent_id || "";
31653
31996
  if (
31654
- target.kind !== "session_setup_record" ||
31997
+ !["setup_snapshot", "session_setup_record"].includes(target.kind) ||
31655
31998
  (!provider && !model && !agentFolder && !agentId)
31656
31999
  ) {
31657
32000
  return null;
@@ -31672,7 +32015,7 @@ Waiting for Claude login output…</pre
31672
32015
  return createHostSchedulerDraft(row, {
31673
32016
  id: "__new__",
31674
32017
  enabled: true,
31675
- targetKind: "session_setup_record",
32018
+ targetKind: "setup_snapshot",
31676
32019
  hostSessionId: "",
31677
32020
  savedSessionId: "",
31678
32021
  sessionSetupRecordId: "",
@@ -31845,7 +32188,7 @@ Waiting for Claude login output…</pre
31845
32188
  "Once at"
31846
32189
  ),
31847
32190
  timezone: baseDraft.timezone || getHostSchedulerTimezone(),
31848
- targetKind: "session_setup_record",
32191
+ targetKind: "setup_snapshot",
31849
32192
  hostSessionId: "",
31850
32193
  savedSessionId: "",
31851
32194
  sessionSetupRecordId: "",
@@ -32120,7 +32463,7 @@ Waiting for Claude login output…</pre
32120
32463
  const currentDraft = getCurrentHostSchedulerSetupDraftForReturn();
32121
32464
  const baseDraft = {
32122
32465
  ...currentDraft,
32123
- targetKind: "session_setup_record",
32466
+ targetKind: "setup_snapshot",
32124
32467
  hostSessionId: "",
32125
32468
  savedSessionId: "",
32126
32469
  sessionSetupRecordId: "",
@@ -33091,7 +33434,7 @@ Waiting for Claude login output…</pre
33091
33434
  const targetKind =
33092
33435
  existingDraft.targetKind === "saved_session"
33093
33436
  ? "saved_session"
33094
- : "session_setup_record";
33437
+ : "setup_snapshot";
33095
33438
  return {
33096
33439
  id: editorEl.getAttribute("data-scheduler-editor-id") || "__new__",
33097
33440
  commandText,
@@ -33187,32 +33530,35 @@ Waiting for Claude login output…</pre
33187
33530
  (record) => record.session_id === draft.savedSessionId
33188
33531
  );
33189
33532
  if (!historyRecord) throw new Error("Choose a saved session target");
33533
+ const snapshot = buildHostSchedulerSessionSetupPayload(draft);
33534
+ snapshot.agent_folder =
33535
+ snapshot.agent_folder || historyRecord.agent_folder || "";
33536
+ if (!snapshot.agent_folder) {
33537
+ throw new Error("Saved session target is missing an agent folder");
33538
+ }
33190
33539
  return {
33191
- kind: "saved_session",
33192
- saved_session_id: historyRecord.session_id,
33540
+ kind: "setup_snapshot",
33193
33541
  agent_id: draft.agentId || historyRecord.agent_id,
33194
- session_setup_payload: draft.sessionSetupPayload || undefined,
33542
+ setup_snapshot: snapshot,
33195
33543
  };
33196
33544
  }
33197
- const sessionSetupRecordId =
33198
- draft.sessionSetupRecordId ||
33199
- `setup-scheduler-${Date.now().toString(36)}`;
33200
33545
  if (!draft.agentId) {
33201
33546
  throw new Error(
33202
33547
  "Session Setup scheduler targets require an Agent ID"
33203
33548
  );
33204
33549
  }
33550
+ const setupSnapshot = buildHostSchedulerSessionSetupPayload(draft);
33551
+ setupSnapshot.agent_folder =
33552
+ draft.agentFolder || setupSnapshot.agent_folder || "";
33553
+ if (!setupSnapshot.agent_folder) {
33554
+ throw new Error(
33555
+ "Session Setup scheduler targets require a start folder"
33556
+ );
33557
+ }
33205
33558
  return {
33206
- kind: "session_setup_record",
33207
- session_setup_record_id: sessionSetupRecordId,
33559
+ kind: "setup_snapshot",
33208
33560
  agent_id: draft.agentId,
33209
- session_setup_payload: buildHostSchedulerSessionSetupPayload(draft),
33210
- session_setup_fields: {
33211
- provider: draft.provider,
33212
- model: draft.model,
33213
- agent_folder: draft.agentFolder,
33214
- permission_mode: draft.permissionMode,
33215
- },
33561
+ setup_snapshot: setupSnapshot,
33216
33562
  };
33217
33563
  }
33218
33564
 
@@ -34039,16 +34385,14 @@ Waiting for Claude login output…</pre
34039
34385
  }
34040
34386
  const liveSession = getSelectedLiveSession();
34041
34387
  const sessionId = liveSession?.sessionId || getActiveSessionId() || "";
34042
- void navigateToRoute(
34043
- buildBrowserRoute({
34044
- target: entryPath,
34045
- sessionId,
34046
- source: "chat",
34047
- returnPath: sessionId
34048
- ? serializeClientRoute(buildChatRoute(sessionId))
34049
- : "",
34050
- })
34051
- );
34388
+ openWebsiteTargetByRuntime({
34389
+ target: entryPath,
34390
+ sessionId,
34391
+ source: "chat",
34392
+ returnPath: sessionId
34393
+ ? serializeClientRoute(buildChatRoute(sessionId))
34394
+ : "",
34395
+ });
34052
34396
  }
34053
34397
 
34054
34398
  function resetStats() {
@@ -39286,15 +39630,18 @@ Waiting for Claude login output…</pre
39286
39630
  return true;
39287
39631
  }
39288
39632
  if (parsed.kind === "host-site") {
39633
+ const siteTarget = `${parsed.targetPath}${parsed.hash || ""}`;
39634
+ if (!isOysterunCapacitorIOSRuntime()) {
39635
+ window.location.assign(siteTarget);
39636
+ return true;
39637
+ }
39289
39638
  const context = getInteractiveLinkRouteContext();
39290
39639
  const sourceContext = getBrowserRouteSourceContext();
39291
- await navigateToRoute(
39292
- buildBrowserRoute({
39293
- target: `${parsed.targetPath}${parsed.hash || ""}`,
39294
- ...sourceContext,
39295
- sessionId: context.sessionId || sourceContext.sessionId || "",
39296
- })
39297
- );
39640
+ openWebsiteTargetByRuntime({
39641
+ target: `${parsed.targetPath}${parsed.hash || ""}`,
39642
+ ...sourceContext,
39643
+ sessionId: context.sessionId || sourceContext.sessionId || "",
39644
+ });
39298
39645
  return true;
39299
39646
  }
39300
39647
  return false;
@@ -39443,8 +39790,11 @@ Waiting for Claude login output…</pre
39443
39790
  const allowedPaths = parseAllowedPathsInput(
39444
39791
  $("create-allowed-paths").value
39445
39792
  );
39793
+ const webAccess = getCreateWebsiteAccessValue();
39794
+ const webPassword =
39795
+ webAccess === "password" ? getCreateWebsitePasswordValue() : "";
39446
39796
 
39447
- return {
39797
+ const payload = {
39448
39798
  provider: createRuntime,
39449
39799
  session_name: getCreateSessionNameValue() || undefined,
39450
39800
  interface_type: normalizeFirstPartyInterfaceType(
@@ -39463,12 +39813,16 @@ Waiting for Claude login output…</pre
39463
39813
  : undefined,
39464
39814
  allow_dangerously_skip_permissions: allowDanger,
39465
39815
  dangerous_mode: allowDanger,
39466
- web_access: getCreateWebsiteAccessValue(),
39816
+ web_access: webAccess,
39467
39817
  notifications_enabled: getCreateNotificationsEnabledValue(),
39468
39818
  ...readTelegramFieldsPayload("create"),
39469
39819
  allowed_paths: allowedPaths,
39470
39820
  runtime_capabilities: readCreateRuntimeCapabilityState(),
39471
39821
  };
39822
+ if (webPassword) {
39823
+ payload.web_password = webPassword;
39824
+ }
39825
+ return payload;
39472
39826
  }
39473
39827
 
39474
39828
  function readCreateFormRequest() {
@@ -40976,6 +41330,23 @@ Waiting for Claude login output…</pre
40976
41330
  data.website?.access ||
40977
41331
  data.rawConfig?.web?.access
40978
41332
  );
41333
+ const websitePasswordInput = $("create-website-password");
41334
+ if (websitePasswordInput) {
41335
+ websitePasswordInput.placeholder =
41336
+ data.website?.password_configured === true
41337
+ ? "Configured"
41338
+ : "";
41339
+ }
41340
+ const websitePasswordStatus = $("create-website-password-status");
41341
+ if (
41342
+ websitePasswordStatus &&
41343
+ getCreateWebsiteAccessValue() === "password"
41344
+ ) {
41345
+ websitePasswordStatus.textContent =
41346
+ data.website?.password_configured === true
41347
+ ? "A Host-local website password is already configured. Enter a new value only to replace it."
41348
+ : "Password mode is locked until this Host-local password is saved to .oysterun/local.json.";
41349
+ }
40979
41350
  setCreateNotificationsEnabledValue(
40980
41351
  data.notifications?.notifications_enabled ??
40981
41352
  data.rawConfig?.notifications?.enabled
@@ -41445,8 +41816,12 @@ Waiting for Claude login output…</pre
41445
41816
  "change",
41446
41817
  markCreatePreferencesEdited
41447
41818
  );
41448
- $("create-website-access").addEventListener(
41449
- "change",
41819
+ $("create-website-access").addEventListener("change", () => {
41820
+ syncCreateWebsitePasswordMode();
41821
+ markCreatePreferencesEdited();
41822
+ });
41823
+ $("create-website-password").addEventListener(
41824
+ "input",
41450
41825
  markCreatePreferencesEdited
41451
41826
  );
41452
41827
  $("create-notifications-enabled").addEventListener(
@@ -41518,7 +41893,6 @@ Waiting for Claude login output…</pre
41518
41893
  "host-notification-web-sound-toggle",
41519
41894
  "host-notification-app-sound-toggle",
41520
41895
  "host-default-provider",
41521
- "host-default-website-access",
41522
41896
  "host-default-notifications-enabled",
41523
41897
  "host-claude-command",
41524
41898
  "host-codex-command",
@@ -41603,6 +41977,8 @@ Waiting for Claude login output…</pre
41603
41977
  window.loadHostSchedulerSchedules = loadHostSchedulerSchedules;
41604
41978
  window.openCurrentAgentProfile = openCurrentAgentProfile;
41605
41979
  window.saveAgentProfileWebsiteAccess = saveAgentProfileWebsiteAccess;
41980
+ window.syncAgentProfileWebsitePasswordMode =
41981
+ syncAgentProfileWebsitePasswordMode;
41606
41982
  window.updateAgentProfileTelegramSettingsDraft =
41607
41983
  updateAgentProfileTelegramSettingsDraft;
41608
41984
  window.saveAgentProfileTelegramSettings =