oysterun 1.5.4 → 1.5.6

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.
@@ -8659,6 +8659,13 @@
8659
8659
  data-session-setup-web-shared="web.enabled"
8660
8660
  />
8661
8661
  </label>
8662
+ <div
8663
+ id="create-website-authority-hint"
8664
+ class="field-hint field-warning"
8665
+ >
8666
+ Website is available only for agents in the Browser
8667
+ Root.
8668
+ </div>
8662
8669
  </div>
8663
8670
  <label
8664
8671
  class="kv-label"
@@ -13325,6 +13332,12 @@ Waiting for Claude login output…</pre
13325
13332
  let lastCreateStartActivationAt = 0;
13326
13333
  let createFormPreferenceVersion = 0;
13327
13334
  let latestCreateFolderConfigRequestId = 0;
13335
+ let createWebsiteAuthorityState = {
13336
+ folderPath: "",
13337
+ projectAgentId: "",
13338
+ eligible: false,
13339
+ reason: "Website is available only for agents in the Browser Root.",
13340
+ };
13328
13341
  let createFormDraftRouteKey = "";
13329
13342
  let createSubmitState = {
13330
13343
  inFlight: false,
@@ -26803,6 +26816,7 @@ Waiting for Claude login output…</pre
26803
26816
  function renderAgentProfileWebsiteAccessRow(liveSession) {
26804
26817
  syncAgentProfileWebsiteAccessSession(liveSession);
26805
26818
  const website = getLiveSessionWebsite(liveSession);
26819
+ const websiteAuthorized = website?.browser_root_authorized === true;
26806
26820
  const enabled = website?.enabled === true;
26807
26821
  const access = normalizeAgentWebAccess(website?.access);
26808
26822
  const websitePassword = getOwnerVisibleWebsitePassword(website);
@@ -26836,16 +26850,27 @@ Waiting for Claude login output…</pre
26836
26850
  type="checkbox"
26837
26851
  data-session-profile-web-enabled="web.enabled"
26838
26852
  ${enabled ? "checked" : ""}
26839
- ${saving ? "disabled" : ""}
26853
+ ${saving || !websiteAuthorized ? "disabled" : ""}
26840
26854
  />
26841
26855
  </label>
26856
+ ${
26857
+ websiteAuthorized
26858
+ ? ""
26859
+ : `<div
26860
+ class="field-hint field-warning"
26861
+ data-session-profile-website-authority="outside_browser_root"
26862
+ >${esc(
26863
+ website?.reason_message ||
26864
+ "Website is available only for agents in the Browser Root."
26865
+ )}</div>`
26866
+ }
26842
26867
  <label class="field compact-field" for="agent-profile-website-access">
26843
26868
  <span>Access</span>
26844
26869
  <select
26845
26870
  id="agent-profile-website-access"
26846
26871
  data-session-profile-web-access="web.access"
26847
26872
  onchange="window.syncAgentProfileWebsitePasswordMode && window.syncAgentProfileWebsitePasswordMode()"
26848
- ${saving ? "disabled" : ""}
26873
+ ${saving || !websiteAuthorized ? "disabled" : ""}
26849
26874
  >${renderAgentWebAccessOptions(access)}</select>
26850
26875
  </label>
26851
26876
  <label
@@ -26863,7 +26888,11 @@ Waiting for Claude login output…</pre
26863
26888
  value="${esc(websitePassword)}"
26864
26889
  data-session-profile-web-local="web.password"
26865
26890
  oninput="window.syncAgentProfileWebsitePasswordMode && window.syncAgentProfileWebsitePasswordMode()"
26866
- ${access === "password" && !saving ? "" : "disabled"}
26891
+ ${
26892
+ access === "password" && !saving && websiteAuthorized
26893
+ ? ""
26894
+ : "disabled"
26895
+ }
26867
26896
  />
26868
26897
  <div
26869
26898
  id="agent-profile-website-password-status"
@@ -26895,7 +26924,7 @@ Waiting for Claude login output…</pre
26895
26924
  data-session-profile-save="website"
26896
26925
  data-session-profile-web-access-save="explicit"
26897
26926
  onclick="window.saveAgentProfileWebsiteAccess && window.saveAgentProfileWebsiteAccess()"
26898
- ${saving ? "disabled" : ""}
26927
+ ${saving || !websiteAuthorized ? "disabled" : ""}
26899
26928
  >${saving ? "Saving..." : "Save"}</button>
26900
26929
  </div>
26901
26930
  ${
@@ -26914,6 +26943,9 @@ Waiting for Claude login output…</pre
26914
26943
  }
26915
26944
 
26916
26945
  function syncAgentProfileWebsitePasswordMode() {
26946
+ const liveSession = getSelectedLiveSession();
26947
+ const websiteAuthorized =
26948
+ getLiveSessionWebsite(liveSession)?.browser_root_authorized === true;
26917
26949
  const access = normalizeAgentWebAccess(
26918
26950
  $("agent-profile-website-access")?.value
26919
26951
  );
@@ -26923,13 +26955,12 @@ Waiting for Claude login output…</pre
26923
26955
  const passwordMode = access === "password";
26924
26956
  if (row) row.classList.remove("hidden");
26925
26957
  if (input) {
26926
- input.disabled = !passwordMode;
26958
+ input.disabled = !websiteAuthorized || !passwordMode;
26927
26959
  }
26928
26960
  if (status) {
26929
- status.textContent = formatOwnerWebsitePasswordHint(
26930
- access,
26931
- input?.value || ""
26932
- );
26961
+ status.textContent = websiteAuthorized
26962
+ ? formatOwnerWebsitePasswordHint(access, input?.value || "")
26963
+ : "Website is available only for agents in the Browser Root.";
26933
26964
  }
26934
26965
  }
26935
26966
 
@@ -26938,6 +26969,19 @@ Waiting for Claude login output…</pre
26938
26969
  if (!liveSession?.sessionId) return;
26939
26970
  syncAgentProfileWebsiteAccessSession(liveSession);
26940
26971
  if (agentProfileWebsiteAccessState.saving === true) return;
26972
+ const previousWebsite = getLiveSessionWebsite(liveSession);
26973
+ if (previousWebsite?.browser_root_authorized !== true) {
26974
+ agentProfileWebsiteAccessState = {
26975
+ ...agentProfileWebsiteAccessState,
26976
+ saving: false,
26977
+ error:
26978
+ previousWebsite?.reason_message ||
26979
+ "Website is available only for agents in the Browser Root.",
26980
+ status: "",
26981
+ };
26982
+ renderAgentProfileView();
26983
+ return;
26984
+ }
26941
26985
  const select = $("agent-profile-website-access");
26942
26986
  const updateConfig = true;
26943
26987
  const nextEnabled =
@@ -26947,7 +26991,6 @@ Waiting for Claude login output…</pre
26947
26991
  nextAccess === "password"
26948
26992
  ? String($("agent-profile-website-password")?.value || "").trim()
26949
26993
  : "";
26950
- const previousWebsite = getLiveSessionWebsite(liveSession);
26951
26994
  if (nextAccess === "password" && !nextWebsitePassword) {
26952
26995
  agentProfileWebsiteAccessState = {
26953
26996
  ...agentProfileWebsiteAccessState,
@@ -37739,6 +37782,59 @@ Waiting for Claude login output…</pre
37739
37782
  syncCreateAllowedPathsShortcutState();
37740
37783
  }
37741
37784
 
37785
+ function isCreateWebsiteEligible() {
37786
+ return (
37787
+ createWebsiteAuthorityState.eligible === true &&
37788
+ createWebsiteAuthorityState.projectAgentId ===
37789
+ String($("create-agent-id")?.value || "").trim()
37790
+ );
37791
+ }
37792
+
37793
+ function syncCreateWebsiteAuthorityState() {
37794
+ const eligible = isCreateWebsiteEligible();
37795
+ const enabledInput = $("create-website-enabled");
37796
+ const accessSelect = $("create-website-access");
37797
+ const hint = $("create-website-authority-hint");
37798
+ if (enabledInput) {
37799
+ enabledInput.disabled = !eligible;
37800
+ if (!eligible) enabledInput.checked = false;
37801
+ }
37802
+ if (accessSelect) {
37803
+ accessSelect.disabled = !eligible;
37804
+ if (!eligible) accessSelect.value = "owner_only";
37805
+ }
37806
+ if (!eligible) setCreateWebsitePasswordValue("");
37807
+ if (hint) {
37808
+ hint.textContent =
37809
+ createWebsiteAuthorityState.reason ||
37810
+ "Website is available only for agents in the Browser Root.";
37811
+ hint.classList.toggle("hidden", eligible);
37812
+ }
37813
+ syncCreateWebsitePasswordMode();
37814
+ }
37815
+
37816
+ function setCreateWebsiteAuthorityState({
37817
+ folderPath = "",
37818
+ projectAgentId = "",
37819
+ eligible = false,
37820
+ reason = "",
37821
+ } = {}) {
37822
+ createWebsiteAuthorityState = {
37823
+ folderPath: String(folderPath || "").trim(),
37824
+ projectAgentId: String(projectAgentId || "").trim(),
37825
+ eligible: eligible === true,
37826
+ reason:
37827
+ String(reason || "").trim() ||
37828
+ "Website is available only for agents in the Browser Root.",
37829
+ };
37830
+ if (eligible !== true) {
37831
+ setCreateWebsiteEnabledValue(false);
37832
+ setCreateWebsiteAccessValue("owner_only");
37833
+ setCreateWebsitePasswordValue("");
37834
+ }
37835
+ syncCreateWebsiteAuthorityState();
37836
+ }
37837
+
37742
37838
  function setCreateWebsiteAccessValue(value) {
37743
37839
  const select = $("create-website-access");
37744
37840
  if (!select) return;
@@ -37774,16 +37870,19 @@ Waiting for Claude login output…</pre
37774
37870
  const row = $("create-website-password-row");
37775
37871
  const input = $("create-website-password");
37776
37872
  const status = $("create-website-password-status");
37873
+ const eligible = isCreateWebsiteEligible();
37777
37874
  const passwordMode = getCreateWebsiteAccessValue() === "password";
37778
37875
  if (row) row.classList.remove("hidden");
37779
37876
  if (input) {
37780
- input.disabled = !passwordMode;
37877
+ input.disabled = !eligible || !passwordMode;
37781
37878
  }
37782
37879
  if (status) {
37783
- status.textContent = formatOwnerWebsitePasswordHint(
37784
- getCreateWebsiteAccessValue(),
37785
- input?.value || ""
37786
- );
37880
+ status.textContent = eligible
37881
+ ? formatOwnerWebsitePasswordHint(
37882
+ getCreateWebsiteAccessValue(),
37883
+ input?.value || ""
37884
+ )
37885
+ : "Website settings are unavailable outside the Browser Root.";
37787
37886
  }
37788
37887
  }
37789
37888
 
@@ -37889,6 +37988,10 @@ Waiting for Claude login output…</pre
37889
37988
  syncSessionSetupInterfaceStyleVisibility();
37890
37989
  setCreateWebsiteEnabledValue(HOST_DEFAULT_SESSION_DEFAULTS.web.enabled);
37891
37990
  setCreateWebsiteAccessValue(HOST_DEFAULT_SESSION_DEFAULTS.web.access);
37991
+ setCreateWebsiteAuthorityState({
37992
+ folderPath: startFolder,
37993
+ eligible: false,
37994
+ });
37892
37995
  setCreateNotificationsEnabledValue(
37893
37996
  HOST_DEFAULT_SESSION_DEFAULTS.notifications.enabled
37894
37997
  );
@@ -37981,6 +38084,10 @@ Waiting for Claude login output…</pre
37981
38084
  syncSessionSetupInterfaceStyleVisibility();
37982
38085
  setCreateWebsiteEnabledValue(HOST_DEFAULT_SESSION_DEFAULTS.web.enabled);
37983
38086
  setCreateWebsiteAccessValue(HOST_DEFAULT_SESSION_DEFAULTS.web.access);
38087
+ setCreateWebsiteAuthorityState({
38088
+ folderPath: record.agent_folder || "",
38089
+ eligible: false,
38090
+ });
37984
38091
  setCreateNotificationsEnabledValue(
37985
38092
  HOST_DEFAULT_SESSION_DEFAULTS.notifications.enabled
37986
38093
  );
@@ -38389,14 +38496,15 @@ Waiting for Claude login output…</pre
38389
38496
  if (liveSession?.website && typeof liveSession.website === "object") {
38390
38497
  return liveSession.website;
38391
38498
  }
38392
- if (
38393
- selectedAgent?.agent_id &&
38394
- selectedAgent.agent_id === liveSession?.agentId &&
38395
- selectedAgent.website
38396
- ) {
38397
- return selectedAgent.website;
38398
- }
38399
- return null;
38499
+ return {
38500
+ enabled: false,
38501
+ available: false,
38502
+ access: "owner_only",
38503
+ browser_root_authorized: false,
38504
+ reason: "session_folder_outside_browser_root",
38505
+ reason_message:
38506
+ "Website is available only for agents in the Browser Root.",
38507
+ };
38400
38508
  }
38401
38509
 
38402
38510
  function getOwnerVisibleWebsitePassword(website = {}) {
@@ -40377,7 +40485,6 @@ Waiting for Claude login output…</pre
40377
40485
  "approval_policy",
40378
40486
  "allow_dangerously_skip_permissions",
40379
40487
  "dangerous_mode",
40380
- "web_access",
40381
40488
  "notifications_enabled",
40382
40489
  "allowed_paths",
40383
40490
  "runtime_capabilities",
@@ -40529,8 +40636,10 @@ Waiting for Claude login output…</pre
40529
40636
  payload.interface_type || getCreateDefaultInterfaceType()
40530
40637
  );
40531
40638
  syncSessionSetupInterfaceStyleVisibility();
40532
- setCreateWebsiteEnabledValue(payload.web_enabled === true);
40533
- setCreateWebsiteAccessValue(payload.web_access || "owner_only");
40639
+ setCreateWebsiteAuthorityState({
40640
+ folderPath: draft.agentFolder || "",
40641
+ eligible: false,
40642
+ });
40534
40643
  setCreateTelegramSettingsValue({});
40535
40644
  $("create-allowed-paths").value = normalizeAllowedPathsForForm(
40536
40645
  payload.allowed_paths
@@ -40603,6 +40712,13 @@ Waiting for Claude login output…</pre
40603
40712
  syncCreateAllowedPathsShortcutState();
40604
40713
  refreshCreateStartButtonEnabled();
40605
40714
  refreshUpdateSessionNameButton();
40715
+ if (draft.agentFolder) {
40716
+ void loadFolderConfigIntoCreateForm(draft.agentFolder, {
40717
+ fillAgentIdIfEmpty: false,
40718
+ applyFormValues: false,
40719
+ showError: false,
40720
+ });
40721
+ }
40606
40722
  }
40607
40723
 
40608
40724
  function readSessionSetupSchedulerDraftForEditor(
@@ -48648,14 +48764,6 @@ Waiting for Claude login output…</pre
48648
48764
  const allowedPaths = isAllowedPathDebugUiVisible()
48649
48765
  ? parseAllowedPathsInput($("create-allowed-paths").value)
48650
48766
  : null;
48651
- const webAccess = getCreateWebsiteAccessValue();
48652
- const webPassword =
48653
- webAccess === "password" ? getCreateWebsitePasswordValue() : "";
48654
- if (webAccess === "password" && !webPassword) {
48655
- throw new Error(
48656
- "Website Password is required when Website Access is Password. Switch Website Access away from Password before clearing it."
48657
- );
48658
- }
48659
48767
 
48660
48768
  const payload = {
48661
48769
  provider: createRuntime,
@@ -48672,8 +48780,6 @@ Waiting for Claude login output…</pre
48672
48780
  createRuntime === "codex" ? codexApproval : undefined,
48673
48781
  allow_dangerously_skip_permissions: allowDanger,
48674
48782
  dangerous_mode: allowDanger,
48675
- web_enabled: getCreateWebsiteEnabledValue(),
48676
- web_access: webAccess,
48677
48783
  notifications_enabled: getCreateNotificationsEnabledValue(),
48678
48784
  install_oysterun_skills: getCreateInstallOysterunSkillsValue(),
48679
48785
  ...readTelegramFieldsPayload("create"),
@@ -48692,9 +48798,6 @@ Waiting for Claude login output…</pre
48692
48798
  ) {
48693
48799
  payload.confirm_beta_provider_permission_mode = true;
48694
48800
  }
48695
- if (webPassword) {
48696
- payload.web_password = webPassword;
48697
- }
48698
48801
  validateTelegramEnabledPayloadOrThrow(
48699
48802
  payload,
48700
48803
  getCreateTelegramConfiguredState()
@@ -48702,6 +48805,23 @@ Waiting for Claude login output…</pre
48702
48805
  return payload;
48703
48806
  }
48704
48807
 
48808
+ function buildCreateWebsitePayload() {
48809
+ if (!isCreateWebsiteEligible()) return null;
48810
+ const webAccess = getCreateWebsiteAccessValue();
48811
+ const webPassword =
48812
+ webAccess === "password" ? getCreateWebsitePasswordValue() : "";
48813
+ if (webAccess === "password" && !webPassword) {
48814
+ throw new Error(
48815
+ "Website Password is required when Website Access is Password. Switch Website Access away from Password before clearing it."
48816
+ );
48817
+ }
48818
+ return {
48819
+ web_enabled: getCreateWebsiteEnabledValue(),
48820
+ web_access: webAccess,
48821
+ ...(webPassword ? { web_password: webPassword } : {}),
48822
+ };
48823
+ }
48824
+
48705
48825
  function readCreateFormRequest() {
48706
48826
  const agent_id = $("create-agent-id").value.trim();
48707
48827
  const agent_folder = $("create-agent-folder").value.trim();
@@ -48724,6 +48844,7 @@ Waiting for Claude login output…</pre
48724
48844
  configToggleChecked
48725
48845
  ),
48726
48846
  sessionPayload: buildCreateSessionPayload(),
48847
+ websitePayload: buildCreateWebsitePayload(),
48727
48848
  };
48728
48849
  }
48729
48850
 
@@ -48754,6 +48875,7 @@ Waiting for Claude login output…</pre
48754
48875
  persistConfig,
48755
48876
  preventConfigCreate,
48756
48877
  sessionPayload,
48878
+ websitePayload,
48757
48879
  } = request;
48758
48880
  const sessionSetupProof = applyCreateSessionSetupProofAttributes(
48759
48881
  buildCreateSessionSetupProviderModelPermissionProof(sessionPayload)
@@ -48791,28 +48913,37 @@ Waiting for Claude login output…</pre
48791
48913
 
48792
48914
  const configPersistenceAction =
48793
48915
  getCreateConfigPersistenceAction(configToggleMode);
48794
- let failureStep = shouldPersistConfig
48795
- ? `Could not ${configPersistenceAction.toLowerCase()} .oysterun/config.json`
48796
- : resumeSessionId
48797
- ? "Could not resume session"
48798
- : "Could not start session";
48916
+ const shouldPersistWebsite = Boolean(websitePayload);
48917
+ let failureStep =
48918
+ shouldPersistConfig || shouldPersistWebsite
48919
+ ? `Could not ${configPersistenceAction.toLowerCase()} .oysterun/config.json`
48920
+ : resumeSessionId
48921
+ ? "Could not resume session"
48922
+ : "Could not start session";
48799
48923
  try {
48800
- if (shouldPersistConfig) {
48924
+ if (shouldPersistConfig || shouldPersistWebsite) {
48801
48925
  await apiWithAuthRetry(
48802
48926
  "PUT",
48803
48927
  "/agent/config",
48804
48928
  {
48805
48929
  agent_id,
48806
48930
  agent_folder,
48807
- ...sessionPayload,
48808
- session_setup_provider_model_permission_proof:
48809
- sessionSetupProof,
48931
+ ...(shouldPersistConfig
48932
+ ? {
48933
+ ...sessionPayload,
48934
+ session_setup_provider_model_permission_proof:
48935
+ sessionSetupProof,
48936
+ }
48937
+ : {}),
48938
+ ...(websitePayload || {}),
48810
48939
  },
48811
48940
  {
48812
48941
  onRetry: () => {
48813
48942
  if (!isCurrentCreateAttempt(attemptId)) return;
48814
48943
  setCreateStatus(
48815
- `${configPersistenceAction} .oysterun/config.json...`,
48944
+ shouldPersistConfig
48945
+ ? `${configPersistenceAction} .oysterun/config.json...`
48946
+ : "Updating Website settings...",
48816
48947
  "Retrying config save after a brief auth settle."
48817
48948
  );
48818
48949
  },
@@ -50190,6 +50321,7 @@ Waiting for Claude login output…</pre
50190
50321
  const folderPath = String(path || "").trim();
50191
50322
  if (!folderPath) {
50192
50323
  hideCreateConfigToggle("");
50324
+ setCreateWebsiteAuthorityState();
50193
50325
  refreshCreateStartButtonEnabled();
50194
50326
  return;
50195
50327
  }
@@ -50207,20 +50339,38 @@ Waiting for Claude login output…</pre
50207
50339
  )}${providerQuery}`
50208
50340
  );
50209
50341
  const resolvedFolderPath = data.agent_folder || folderPath;
50342
+ const currentFolderPath = String(
50343
+ $("create-agent-folder").value || ""
50344
+ ).trim();
50345
+ if (
50346
+ requestId !== latestCreateFolderConfigRequestId ||
50347
+ currentFolderPath !== folderPath
50348
+ ) {
50349
+ return;
50350
+ }
50351
+ setCreateWebsiteAuthorityState({
50352
+ folderPath: resolvedFolderPath,
50353
+ projectAgentId: data.websiteAgentId || "",
50354
+ eligible:
50355
+ data.websiteEligible === true ||
50356
+ data.website?.browser_root_authorized === true,
50357
+ reason:
50358
+ data.website?.reason_message ||
50359
+ "Website is available only for agents in the Browser Root.",
50360
+ });
50361
+ if (opts.applyFormValues === false && isCreateWebsiteEligible()) {
50362
+ setCreateWebsiteEnabledValue(
50363
+ HOST_DEFAULT_SESSION_DEFAULTS.web.enabled
50364
+ );
50365
+ setCreateWebsiteAccessValue(
50366
+ HOST_DEFAULT_SESSION_DEFAULTS.web.access
50367
+ );
50368
+ }
50210
50369
  applyCreateConfigToggleMode(
50211
50370
  resolvedFolderPath,
50212
50371
  data.hasConfig === true
50213
50372
  );
50214
50373
  if (opts.applyFormValues !== false) {
50215
- const currentFolderPath = String(
50216
- $("create-agent-folder").value || ""
50217
- ).trim();
50218
- if (
50219
- requestId !== latestCreateFolderConfigRequestId ||
50220
- currentFolderPath !== folderPath
50221
- ) {
50222
- return;
50223
- }
50224
50374
  const preferencesUntouched =
50225
50375
  createFormPreferenceVersion === requestPreferenceVersion;
50226
50376
  $("create-agent-folder").value = resolvedFolderPath;
@@ -50307,6 +50457,10 @@ Waiting for Claude login output…</pre
50307
50457
  refreshCreateStartButtonEnabled();
50308
50458
  } catch (err) {
50309
50459
  hideCreateConfigToggle(folderPath);
50460
+ setCreateWebsiteAuthorityState({
50461
+ folderPath,
50462
+ eligible: false,
50463
+ });
50310
50464
  setCreateProviderSkillStatus(null);
50311
50465
  setCreateProviderTrustStatus(null);
50312
50466
  refreshCreateStartButtonEnabled();
@@ -50827,6 +50981,10 @@ Waiting for Claude login output…</pre
50827
50981
  markCreatePreferencesEdited();
50828
50982
  syncCreateAllowedPathsShortcutState();
50829
50983
  hideCreateConfigToggle($("create-agent-folder").value);
50984
+ setCreateWebsiteAuthorityState({
50985
+ folderPath: $("create-agent-folder").value,
50986
+ eligible: false,
50987
+ });
50830
50988
  refreshUpdateSessionNameButton();
50831
50989
  void refreshCreateProviderTrustStatus();
50832
50990
  refreshCreateStartButtonEnabled();
@@ -50834,6 +50992,7 @@ Waiting for Claude login output…</pre
50834
50992
  $("create-agent-id").addEventListener("input", () => {
50835
50993
  markCreatePreferencesEdited();
50836
50994
  syncCreateSessionNameDefault();
50995
+ syncCreateWebsiteAuthorityState();
50837
50996
  void refreshCreateProviderSkillStatus();
50838
50997
  void refreshCreateProviderTrustStatus();
50839
50998
  refreshCreateStartButtonEnabled();
@@ -35,6 +35,41 @@ skills/
35
35
  Those development roots remain mirror/source parity inputs for source-static and
36
36
  package verification. They are not an installed runtime precondition.
37
37
 
38
+ ## Canonical Aggregate And Schema
39
+
40
+ The hand-maintained aggregate source is:
41
+
42
+ ```text
43
+ skills/Oysterun/SKILL.md
44
+ ```
45
+
46
+ The mirror command copies that file to:
47
+
48
+ ```text
49
+ .codex/skills/Oysterun/SKILL.md
50
+ host-service/assets/product-skills/Oysterun/SKILL.md
51
+ ```
52
+
53
+ The eight module sources remain `skills/oysterun-*/`. Their provider-facing
54
+ module trees, including `skills/Oysterun/modules/`, are generated mirrors.
55
+
56
+ Oysterun-owned aggregate and module `SKILL.md` files require YAML frontmatter
57
+ with a non-empty lowercase hyphen-case `name` and a non-empty `description`.
58
+ The aggregate metadata name is `oysterun`; its uppercase `Oysterun` installation
59
+ folder remains the P88 path contract.
60
+
61
+ Run:
62
+
63
+ ```bash
64
+ node tool_scripts/sync_product_skills_to_codex.mjs
65
+ node tool_scripts/check_product_skill_mirrors.mjs
66
+ ```
67
+
68
+ Sync validates every canonical input before modifying a generated mirror.
69
+ Mirror checks and npm packaging fail when canonical/generated Oysterun skills
70
+ are invalid or stale. This validation is a source/release guard only; Host
71
+ startup and P88 install/update do not reject arbitrary user-owned skills.
72
+
38
73
  ## Ownership And Copy Rules
39
74
 
40
75
  The aggregate `Oysterun/SKILL.md` must contain:
@@ -1,3 +1,8 @@
1
+ ---
2
+ name: oysterun
3
+ description: Use as the aggregate index for Oysterun product skills installed by Host. Route operations to the specific sessions, session-chat, context, scheduler, mail, notifications, website, or Telegram module skill.
4
+ ---
5
+
1
6
  # Oysterun
2
7
 
3
8
  <!-- oysterun-skill-set: true -->
@@ -28,3 +33,7 @@ can insert stable relative paths such as:
28
33
 
29
34
  Do not remove the ownership marker above. The Host refuses to overwrite an
30
35
  existing `Oysterun` provider skill folder unless this marker is present.
36
+
37
+ After selecting a module, read and follow that module's `SKILL.md`. The
38
+ aggregate is only a discovery and routing index; it does not replace any
39
+ module-specific safety, authorization, confirmation, or command contract.
@@ -12,24 +12,26 @@ Canonical product skill source lives at `skills/oysterun-scheduler/`. The repo-l
12
12
  Use this module skill for normal product scheduler operations. It wraps the P86 product CLI contract:
13
13
 
14
14
  ```text
15
- oysterun scheduler <action> [options]
15
+ $OYSTERUN_CLI_BIN scheduler <action> [options]
16
16
  ```
17
17
 
18
- The helper delegates to the same CLI surface:
18
+ An external repo operator may use the generated helper, which delegates to the
19
+ same CLI surface:
19
20
 
20
21
  ```bash
21
22
  node .codex/skills/Oysterun/modules/oysterun-scheduler/scripts/oysterun_scheduler.mjs list --host http://localhost:2322
22
23
  ```
23
24
 
24
- When Host injects this skill into a live session, the helper path is:
25
+ When Host injects this skill into a live Claude or Codex session, always use:
25
26
 
26
27
  ```bash
27
- node .claude/skills/Oysterun/modules/oysterun-scheduler/scripts/oysterun_scheduler.mjs list
28
+ $OYSTERUN_CLI_BIN scheduler list
28
29
  ```
29
30
 
30
31
  ## Commands
31
32
 
32
33
  - `list`
34
+ - `create --prompt <text> --frequency daily --time HH:mm`
33
35
  - `create --session-ref <display-name> --prompt <text> --frequency daily --time HH:mm`
34
36
  - `get`
35
37
  - `update`
@@ -42,19 +44,40 @@ node .claude/skills/Oysterun/modules/oysterun-scheduler/scripts/oysterun_schedul
42
44
 
43
45
  For Host scheduler rows, `create` targets a portable outside-scheduler setup
44
46
  snapshot. In a live Host session, omit the target to use the current session's
45
- saved setup, or pass `--session-ref <display-name>` for another session. Use
47
+ saved setup, or pass `--session-ref <display-name>` for another session. The
48
+ resolved session agent folder must be a valid, non-conflicting project under
49
+ the Host Default Browse Root. Use
46
50
  rule fields such as `--frequency daily --time HH:mm`, `--frequency weekly
47
51
  --weekdays monday,wednesday --time HH:mm`, or `--frequency once --run-at
48
52
  <ISO time>`. Do not use `--interval` for Host scheduler rows; use `chat loop
49
53
  create --interval ...` for in-session loops.
50
54
 
55
+ ## First Scheduler Initialization
56
+
57
+ The Host owns first-file initialization. For a valid Browser Root project with
58
+ no `.oysterun/schedulers.json`, run the normal create command:
59
+
60
+ ```bash
61
+ $OYSTERUN_CLI_BIN scheduler create --prompt "..." --frequency daily --time 09:00
62
+ ```
63
+
64
+ The Host validates Browser Root authority, copies the current session setup,
65
+ and atomically creates the first portable Scheduler file. Do not manually
66
+ create an empty Scheduler JSON file, write Host SQLite rows, or call a
67
+ `/scheduler/*` endpoint to bootstrap the project.
68
+
69
+ If create reports that the folder is not a Browser Root project, select or move
70
+ the project under Default Browse Root and retry. If it reports a project
71
+ collision or invalid portable file, resolve the stated conflict or repair the
72
+ existing file; do not overwrite it with an empty schema.
73
+
51
74
  ## Scheduler Setup Snapshot Contract
52
75
 
53
76
  When creating or updating a Host scheduler, prefer an existing session config:
54
77
 
55
78
  ```bash
56
- node .claude/skills/Oysterun/modules/oysterun-scheduler/scripts/oysterun_scheduler.mjs create --prompt "..." --frequency daily --time 09:00
57
- node .claude/skills/Oysterun/modules/oysterun-scheduler/scripts/oysterun_scheduler.mjs create --session-ref "My Agent Session" --prompt "..." --frequency daily --time 09:00
79
+ $OYSTERUN_CLI_BIN scheduler create --prompt "..." --frequency daily --time 09:00
80
+ $OYSTERUN_CLI_BIN scheduler create --session-ref "My Agent Session" --prompt "..." --frequency daily --time 09:00
58
81
  ```
59
82
 
60
83
  Do not import Host scheduler internals, call `/scheduler/*` endpoints directly,
@@ -70,9 +93,10 @@ the injected binary:
70
93
  $OYSTERUN_CLI_BIN mail send --title "Report ready" --html-file data/latest_report.html
71
94
  ```
72
95
 
73
- Do not send markdown, auto-format, stdin, `--text`, `--body`, or raw HTML held
74
- only in an environment variable. Do not call Host `/mail/*` endpoints directly.
75
- The `.html` file extension is the Mail deliverable contract.
96
+ For a durable report, do not send markdown, auto-format, stdin, `--text`,
97
+ `--body`, or raw HTML held only in an environment variable. Do not call Host
98
+ `/mail/*` endpoints directly. The `.html` file extension is the durable Mail
99
+ report contract.
76
100
 
77
101
  If a route explicitly requires an explicit setup snapshot instead of a current
78
102
  or saved session, the snapshot must include the full provider runtime proof:
@@ -99,12 +123,15 @@ the persisted scheduler target is valid.
99
123
 
100
124
  Scheduler jobs that need to send Oysterun Mail should use the normal product
101
125
  Mail CLI, not a separate scheduler-only Mail stack. Inside a Host-injected
102
- scheduler/runtime environment, prefer:
126
+ scheduler/runtime environment, a short non-report status may use:
103
127
 
104
128
  ```bash
105
129
  $OYSTERUN_CLI_BIN mail send --title "Digest ready" --text "Daily tracker finished"
106
130
  ```
107
131
 
132
+ If the job produced a report, use the durable `.html` plus `--html-file`
133
+ contract above instead of `--text`.
134
+
108
135
  The Host injects a scheduler-run scoped `mail:create` capability for Mail send
109
136
  only. The CLI preserves `OYSTERUN_SCHEDULE_ID`,
110
137
  `OYSTERUN_SCHEDULE_RUN_ID`, and `OYSTERUN_AGENT_ID` as Mail attribution. Do