oysterun 1.5.3-beta.2 → 1.5.3

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 (31) hide show
  1. package/README.md +144 -0
  2. package/dev/client/web/index.html +240 -13
  3. package/dev/client/web-chat/dist/assets/{ReactPrism-BGRmaJf7.js → ReactPrism-Bny_C7xD.js} +1 -1
  4. package/dev/client/web-chat/dist/assets/{ReactPrism-BGRmaJf7.js.map → ReactPrism-Bny_C7xD.js.map} +1 -1
  5. package/dev/client/web-chat/dist/assets/{index-CRrbvWg7.js → index-Bo9Y7lbT.js} +2 -2
  6. package/dev/client/web-chat/dist/assets/index-Bo9Y7lbT.js.map +1 -0
  7. package/dev/client/web-chat/dist/assets/{index-W5ISbeWM.js → index-CIswQzBj.js} +1 -1
  8. package/dev/client/web-chat/dist/assets/{index-W5ISbeWM.js.map → index-CIswQzBj.js.map} +1 -1
  9. package/dev/client/web-chat/dist/assets/{index-Cuy-f6-d.js → index-CqfVnL4A.js} +74 -74
  10. package/dev/client/web-chat/dist/assets/index-CqfVnL4A.js.map +1 -0
  11. package/dev/client/web-chat/dist/assets/{pdf-BFZzIBtl.js → pdf-0XtIzGl2.js} +3 -3
  12. package/dev/client/web-chat/dist/assets/pdf-0XtIzGl2.js.map +1 -0
  13. package/dev/client/web-chat/dist/assets/{web-B2-C3qO8.js → web-B5Lh8s9A.js} +1 -1
  14. package/dev/client/web-chat/dist/assets/{web-B2-C3qO8.js.map → web-B5Lh8s9A.js.map} +1 -1
  15. package/dev/client/web-chat/dist/assets/{web-DUrVmksC.js → web-CZMJKFKu.js} +1 -1
  16. package/dev/client/web-chat/dist/assets/{web-DUrVmksC.js.map → web-CZMJKFKu.js.map} +1 -1
  17. package/dev/client/web-chat/dist/index.html +1 -1
  18. package/host-service/browser-root-project-index.mjs +30 -0
  19. package/host-service/matrix-transcript-read-adapter.mjs +80 -1
  20. package/host-service/package.json +1 -1
  21. package/host-service/routec-matrix-sqlite-store.mjs +49 -0
  22. package/host-service/routec-matrix-storage-adapter.mjs +36 -0
  23. package/host-service/scheduler-acp-provider-runner.mjs +480 -0
  24. package/host-service/scheduler-service.mjs +100 -33
  25. package/host-service/server.mjs +168 -132
  26. package/host-service/session-list-preview-service.mjs +124 -0
  27. package/host-service/session-manager.mjs +60 -19
  28. package/package.json +1 -1
  29. package/dev/client/web-chat/dist/assets/index-CRrbvWg7.js.map +0 -1
  30. package/dev/client/web-chat/dist/assets/index-Cuy-f6-d.js.map +0 -1
  31. package/dev/client/web-chat/dist/assets/pdf-BFZzIBtl.js.map +0 -1
package/README.md CHANGED
@@ -1,5 +1,149 @@
1
1
  # Oysterun
2
2
 
3
+ ## What Oysterun Is
4
+
5
+ Oysterun is a deployment and operations platform for working AI agents. It lets
6
+ people run Claude Code or Codex on their own Mac or Linux Host, then operate
7
+ those agents from an iPhone or web browser with persistent sessions,
8
+ scheduling, notifications, reports, websites, local files, and agent skills.
9
+
10
+ The core idea is that prompts and skills are not enough. A useful agent also
11
+ needs a runtime, tools, permissions, scheduled execution, a readable delivery
12
+ surface, and a reliable way for its user to intervene. Oysterun brings those
13
+ pieces together so an agent can move from "only its author can run it" toward a
14
+ complete AgentFolder that another user can install and operate.
15
+
16
+ ## Build Week Scope
17
+
18
+ ### Introduction
19
+
20
+ Oysterun is an existing platform that was created before OpenAI Build Week. The
21
+ submission does not claim that the complete Oysterun Host, client, session,
22
+ scheduling, Mail, Website, notification, or skill system was built during the
23
+ event.
24
+
25
+ For this submission, Build Week work is measured from July 14, 2026 at 00:00
26
+ Asia/Taipei, equivalent to July 13, 2026 at 09:00 Pacific Time. The final
27
+ repository commit before that cutoff is `cf9c8e11` (July 13 at 20:31
28
+ Asia/Taipei), and the submission snapshot is `711c0246` (July 20 at 23:39
29
+ Asia/Taipei). The range `cf9c8e11..711c0246` contains 39 commits including
30
+ merges and changes 159 distinct files.
31
+
32
+ The work in that measured range focuses on making complete agents easier to
33
+ distribute and use:
34
+
35
+ - **Stock Research Agent:** a cloneable AgentFolder that researches stocks,
36
+ generates readable HTML reports, publishes them through Oysterun Website, and
37
+ can deliver them through Oysterun Mail.
38
+ - **GitHub Tracker Agent:** a cloneable AgentFolder that monitors public GitHub
39
+ repositories, runs on a schedule, and publishes update reports.
40
+ - **Direct GitHub Agent Store:** a new Oysterun flow that accepts a public
41
+ GitHub repository URL, safely clones the AgentFolder into the Host, resolves
42
+ its configuration, starts a session, and opens the resulting chat.
43
+
44
+ This Build Week submission demonstrates these new agents and the Agent Store
45
+ workflow running on the existing Oysterun platform.
46
+
47
+ The dated Git history and the submitted Codex `/feedback` Session ID provide
48
+ evidence of the work completed during the submission period.
49
+
50
+ ### Evidence
51
+
52
+ 1. Important Commits:
53
+ - SQLite migration and tool-detail integration, July 14: `56690244` through `7a2e304e`
54
+ - Provider aggregation and Telegram delivery follow-ups, July 14:
55
+ `523c68d9` through `f6c66f58`
56
+ - Timeline and pagination recovery, July 16:
57
+ `212a9fd3` through `2caf0fa2`
58
+ - Browser Root AgentFolder registration, July 19: `87e6df47`
59
+ - Direct GitHub Agent Store clone/start demo, July 20: `ba6bbdfa`
60
+ 2. Team Session:
61
+ 1. PM: 019f5761-**1078**-7dc3-a130-64ac66435cc8
62
+ 2. Team Lead: 019f5760-ee50-70e3-870e-1d16838305f2
63
+ 3. Assistant1: 019f56e8-4c9f-71b2-8ad5-a0232b95686d
64
+ 4. Assistant2: 019f552a-3830-7cf3-92ca-dd483fa63cf9
65
+ 5. Engineer: 019f56fc-c4cd-7120-a2ff-d2c603ebafa7
66
+ 6. Manual Session: 019f05d5-f491-7db3-8bf6-94eba3c89607
67
+ 3. Other Repo (Agents):
68
+ 1. https://github.com/GoatWang/oysterun-ccstock-workenv-en
69
+ 2. https://github.com/GoatWang/oysterun-github-tracker
70
+
71
+ ## How Codex And GPT-5.6 Were Used
72
+
73
+ The work measured from the July 14, 00:00 Asia/Taipei cutoff was developed with
74
+ Codex as the primary engineering environment and GPT-5.6 as the reasoning model
75
+ for planning, codebase investigation, implementation, verification design,
76
+ debugging, and review.
77
+
78
+ Rather than using Codex only for isolated code generation, Oysterun was
79
+ developed by a persistent AI engineering team running through Oysterun itself:
80
+
81
+ - two Assistants prepared requirements, verification plans, and progress
82
+ summaries;
83
+ - one Project Manager coordinated tasks and checked progress every two minutes;
84
+ - one Team Lead reviewed architecture, verification evidence, and priorities;
85
+ - three Engineers implemented and tested scoped changes;
86
+ - every 30 minutes, an Assistant summarized team progress for human review.
87
+
88
+ The human Owner made the product, security, UX, and release decisions. Codex
89
+ traced behavior across the Host, web client, Matrix facade, provider adapters,
90
+ skills, and operational scripts; implemented the approved phases; and produced
91
+ focused verification evidence. This workflow let the project continue making
92
+ measurable progress while the Owner worked full-time and while the Oysterun Host
93
+ ran remotely on a Mac mini in Taiwan.
94
+
95
+ ## Judge Quickstart
96
+
97
+ ### Hosted review environment (recommended)
98
+
99
+ A dedicated Oysterun Host URL and its login password are provided privately in
100
+ the project registration form. Judges should follow the access instructions in
101
+ that form; it is the authoritative source for the current Host address and
102
+ credentials. No local installation or rebuild is required for this path.
103
+
104
+ Available clients:
105
+
106
+ 1. **iOS app**
107
+ - Install [Oysterun from the App Store](https://apps.apple.com/tw/app/oysterun/id6762069229).
108
+ - Add the Host URL supplied in the registration form.
109
+ - Sign in with the supplied Host password.
110
+ 2. **Web browser**
111
+ - Open the Host `/app/` URL supplied in the registration form.
112
+ - Sign in with the supplied Host password.
113
+
114
+ Both clients connect to the same dedicated Host and expose the same sessions.
115
+ The password is intentionally not stored in this public repository.
116
+
117
+ ## What Judges Should Test
118
+
119
+ 1. Open the `oysterun-ccstock-workenv-en-da14e` session.
120
+ 2. Ask any question, then ask the agent to generate a report.
121
+ 3. Open the website URL returned by the agent.
122
+ 4. Check the mail box
123
+ 5. Ask the agent to create a schedule or send you a Mail item.
124
+ 6. Open the Webiste in the external browser
125
+
126
+ ## Oysterun Skills
127
+
128
+ The repository includes first-party skills that let Codex operate
129
+ Oysterun through its supported CLI rather than direct Host API calls:
130
+
131
+ - `skills/oysterun-sessions`
132
+ - `skills/oysterun-session-chat`
133
+ - `skills/oysterun-find-context`
134
+ - `skills/oysterun-scheduler`
135
+ - `skills/oysterun-notifications`
136
+ - `skills/oysterun-mail`
137
+ - `skills/oysterun-telegram`
138
+ - `skills/oysterun-website`
139
+
140
+ `skills/Oysterun` is the umbrella skill set used when Oysterun installs or
141
+ updates the product skills in an AgentFolder.
142
+
143
+ ------------------------------------OpenAI Build Week------------------------------------
144
+
145
+ ## Runtime Pieces
146
+
3
147
  Oysterun currently has three main runtime pieces:
4
148
 
5
149
  - `backend/`: the Cloud API written in FastAPI
@@ -12183,6 +12183,7 @@ Waiting for Claude login output…</pre
12183
12183
  lastTestRunScheduleId: "",
12184
12184
  lastTestRunRunId: "",
12185
12185
  lastTestRunOutcome: "",
12186
+ lastTimezoneReconciliationGeneration: 0,
12186
12187
  error: "",
12187
12188
  status: "",
12188
12189
  };
@@ -13191,6 +13192,8 @@ Waiting for Claude login output…</pre
13191
13192
  let projectRegistrationIssues = [];
13192
13193
  let liveSessions = [];
13193
13194
  let sessionHistory = [];
13195
+ const sessionListPreviewBySessionId = new Map();
13196
+ let sessionListPreviewHydrationRequestId = 0;
13194
13197
  let matrixTranscriptSessionIdsByAgent = {};
13195
13198
  let matrixTranscriptMembershipLoadedAtByAgent = {};
13196
13199
  const matrixTranscriptMembershipRequestsByAgent = new Map();
@@ -25009,6 +25012,7 @@ Waiting for Claude login output…</pre
25009
25012
  catalog: createDashboardDataRequestSlot(),
25010
25013
  sessions: createDashboardDataRequestSlot(),
25011
25014
  history: createDashboardDataRequestSlot(),
25015
+ previews: createDashboardDataRequestSlot(),
25012
25016
  providers: createDashboardDataRequestSlot(),
25013
25017
  };
25014
25018
  }
@@ -25039,13 +25043,28 @@ Waiting for Claude login output…</pre
25039
25043
  }
25040
25044
 
25041
25045
  function invalidateDashboardDataCaches(opts = {}) {
25042
- const keys = ["catalog", "sessions", "history", "providers"];
25046
+ const keys = [
25047
+ "catalog",
25048
+ "sessions",
25049
+ "history",
25050
+ "previews",
25051
+ "providers",
25052
+ ];
25043
25053
  const resetAll = opts.all === true;
25044
25054
  for (const key of keys) {
25045
- if (resetAll || opts[key] === true) {
25055
+ if (
25056
+ resetAll ||
25057
+ opts[key] === true ||
25058
+ (key === "previews" &&
25059
+ (opts.sessions === true || opts.history === true))
25060
+ ) {
25046
25061
  resetDashboardDataRequestSlot(dashboardDataRequestState[key]);
25047
25062
  }
25048
25063
  }
25064
+ if (resetAll) {
25065
+ sessionListPreviewBySessionId.clear();
25066
+ sessionListPreviewHydrationRequestId += 1;
25067
+ }
25049
25068
  if (
25050
25069
  resetAll ||
25051
25070
  opts.transcripts === true ||
@@ -25138,13 +25157,27 @@ Waiting for Claude login output…</pre
25138
25157
  }
25139
25158
 
25140
25159
  function readDashboardLiveSessions(opts = {}) {
25141
- return readDashboardDataRequest("sessions", "/sessions", {
25142
- force: opts.force === true,
25143
- });
25160
+ return readDashboardDataRequest(
25161
+ "sessions",
25162
+ "/sessions?include_previews=0",
25163
+ {
25164
+ force: opts.force === true,
25165
+ }
25166
+ );
25144
25167
  }
25145
25168
 
25146
25169
  function readDashboardSessionHistory(opts = {}) {
25147
- return readDashboardDataRequest("history", "/sessions/history", {
25170
+ return readDashboardDataRequest(
25171
+ "history",
25172
+ "/sessions/history?include_previews=0",
25173
+ {
25174
+ force: opts.force === true,
25175
+ }
25176
+ );
25177
+ }
25178
+
25179
+ function readDashboardSessionPreviews(opts = {}) {
25180
+ return readDashboardDataRequest("previews", "/sessions/previews", {
25148
25181
  force: opts.force === true,
25149
25182
  });
25150
25183
  }
@@ -31242,6 +31275,7 @@ Waiting for Claude login output…</pre
31242
31275
  ),
31243
31276
  }));
31244
31277
  sessionHistory = historyData.sessions || [];
31278
+ pruneSessionListPreviewCache();
31245
31279
  explorerState.roots = catalogData.roots || {};
31246
31280
  pruneMatrixTranscriptMembershipCache();
31247
31281
  const selectedLiveSession = getSelectedLiveSession();
@@ -31257,6 +31291,7 @@ Waiting for Claude login output…</pre
31257
31291
  if (keepConnectedSelectedSession) {
31258
31292
  renderExplorerRoots();
31259
31293
  renderCatalog();
31294
+ void hydrateSessionListPreviews();
31260
31295
  renderWebCatalog();
31261
31296
  void refreshMailUnreadCount({ quiet: true });
31262
31297
  updateComposerAvailability();
@@ -31270,6 +31305,7 @@ Waiting for Claude login output…</pre
31270
31305
  }
31271
31306
  renderExplorerRoots();
31272
31307
  renderCatalog();
31308
+ void hydrateSessionListPreviews();
31273
31309
  renderWebCatalog();
31274
31310
  void refreshMailUnreadCount({ quiet: true });
31275
31311
  const activeSelectedSession = selectedSessionId
@@ -31516,6 +31552,142 @@ Waiting for Claude login output…</pre
31516
31552
  return `${providerLabel} · ${modelLabel} · ${folderName}`;
31517
31553
  }
31518
31554
 
31555
+ function getSessionListPreviewState(sessionId) {
31556
+ const normalizedSessionId = sanitizeRouteText(sessionId);
31557
+ return normalizedSessionId
31558
+ ? sessionListPreviewBySessionId.get(normalizedSessionId) || null
31559
+ : null;
31560
+ }
31561
+
31562
+ function getSessionListPreviewText({
31563
+ sessionId,
31564
+ lastMessagePreview,
31565
+ runtime,
31566
+ model,
31567
+ folder,
31568
+ }) {
31569
+ const state = getSessionListPreviewState(sessionId);
31570
+ const resolvedPreview =
31571
+ state?.loaded === true
31572
+ ? state.lastMessagePreview
31573
+ : lastMessagePreview;
31574
+ if (state?.loaded !== true && !resolvedPreview) {
31575
+ return "Loading latest message...";
31576
+ }
31577
+ return getSessionPreviewText({
31578
+ lastMessagePreview: resolvedPreview,
31579
+ runtime,
31580
+ model,
31581
+ folder,
31582
+ });
31583
+ }
31584
+
31585
+ function updateSessionListPreviewDom() {
31586
+ for (const row of document.querySelectorAll(
31587
+ "[data-live-session-id], [data-history-session-id]"
31588
+ )) {
31589
+ const liveSessionId = sanitizeRouteText(
31590
+ row.getAttribute("data-live-session-id")
31591
+ );
31592
+ const historySessionId = sanitizeRouteText(
31593
+ row.getAttribute("data-history-session-id")
31594
+ );
31595
+ const sessionId = liveSessionId || historySessionId;
31596
+ if (!sessionId) continue;
31597
+ const liveSession = liveSessionId
31598
+ ? liveSessions.find((entry) => entry.sessionId === sessionId)
31599
+ : null;
31600
+ const historyRecord = historySessionId
31601
+ ? sessionHistory.find((entry) => entry.session_id === sessionId)
31602
+ : null;
31603
+ const source = liveSession || historyRecord;
31604
+ if (!source) continue;
31605
+ const state = getSessionListPreviewState(sessionId);
31606
+ row.dataset.sessionPreviewStatus = state?.status || "loading";
31607
+ const preview = row.querySelector(".session-row-preview");
31608
+ if (!preview) continue;
31609
+ preview.textContent = getSessionListPreviewText({
31610
+ sessionId,
31611
+ lastMessagePreview:
31612
+ liveSession?.lastMessagePreview ||
31613
+ historyRecord?.last_message_preview ||
31614
+ "",
31615
+ runtime: source.provider || source.runtime || "claude",
31616
+ model: source.model || "default model",
31617
+ folder: source.cwd || source.agent_folder || "",
31618
+ });
31619
+ }
31620
+ }
31621
+
31622
+ function pruneSessionListPreviewCache() {
31623
+ const activeSessionIds = new Set([
31624
+ ...liveSessions.map((entry) => entry.sessionId),
31625
+ ...sessionHistory.map((entry) => entry.session_id),
31626
+ ]);
31627
+ for (const sessionId of sessionListPreviewBySessionId.keys()) {
31628
+ if (!activeSessionIds.has(sessionId)) {
31629
+ sessionListPreviewBySessionId.delete(sessionId);
31630
+ }
31631
+ }
31632
+ }
31633
+
31634
+ async function hydrateSessionListPreviews(opts = {}) {
31635
+ if (!liveSessions.length && !sessionHistory.length) return;
31636
+ const requestId = ++sessionListPreviewHydrationRequestId;
31637
+ try {
31638
+ const data = await readDashboardSessionPreviews({
31639
+ force: opts.force === true,
31640
+ });
31641
+ if (requestId !== sessionListPreviewHydrationRequestId) return;
31642
+ for (const entry of Array.isArray(data.previews)
31643
+ ? data.previews
31644
+ : []) {
31645
+ const sessionId = sanitizeRouteText(entry.session_id);
31646
+ if (!sessionId) continue;
31647
+ sessionListPreviewBySessionId.set(sessionId, {
31648
+ loaded: true,
31649
+ status: sanitizeRouteText(entry.status) || "unavailable",
31650
+ lastMessagePreview:
31651
+ typeof entry.last_message_preview === "string"
31652
+ ? entry.last_message_preview
31653
+ : "",
31654
+ latestCommittedSeq: Number(entry.latest_committed_seq) || 0,
31655
+ cacheStatus: sanitizeRouteText(entry.cache_status),
31656
+ errorCode: sanitizeRouteText(entry.error_code),
31657
+ });
31658
+ }
31659
+ updateSessionListPreviewDom();
31660
+ } catch (err) {
31661
+ if (requestId !== sessionListPreviewHydrationRequestId) return;
31662
+ for (const session of liveSessions) {
31663
+ if (!sessionListPreviewBySessionId.has(session.sessionId)) {
31664
+ sessionListPreviewBySessionId.set(session.sessionId, {
31665
+ loaded: true,
31666
+ status: "unavailable",
31667
+ lastMessagePreview: "",
31668
+ latestCommittedSeq: 0,
31669
+ cacheStatus: "request_failed",
31670
+ errorCode: "session_preview_request_failed",
31671
+ });
31672
+ }
31673
+ }
31674
+ for (const record of sessionHistory) {
31675
+ if (!sessionListPreviewBySessionId.has(record.session_id)) {
31676
+ sessionListPreviewBySessionId.set(record.session_id, {
31677
+ loaded: true,
31678
+ status: "unavailable",
31679
+ lastMessagePreview: "",
31680
+ latestCommittedSeq: 0,
31681
+ cacheStatus: "request_failed",
31682
+ errorCode: "session_preview_request_failed",
31683
+ });
31684
+ }
31685
+ }
31686
+ updateSessionListPreviewDom();
31687
+ console.warn("[oysterun-ui] Session preview hydration failed", err);
31688
+ }
31689
+ }
31690
+
31519
31691
  function renderSessionAvatar(runtime, isActive = false) {
31520
31692
  const key = getSessionRuntimeVisualKey(runtime);
31521
31693
  return `
@@ -31883,7 +32055,9 @@ Waiting for Claude login output…</pre
31883
32055
  const lastTouched = formatSessionRelativeTime(
31884
32056
  session.lastActiveAt || session.startedAt
31885
32057
  );
31886
- const previewText = getSessionPreviewText({
32058
+ const previewState = getSessionListPreviewState(session.sessionId);
32059
+ const previewText = getSessionListPreviewText({
32060
+ sessionId: session.sessionId,
31887
32061
  lastMessagePreview: session.lastMessagePreview,
31888
32062
  runtime,
31889
32063
  model,
@@ -31895,6 +32069,9 @@ Waiting for Claude login output…</pre
31895
32069
  class="agent-entry-card session-list-row session-list-row-live"
31896
32070
  data-live-session-id="${esc(session.sessionId)}"
31897
32071
  data-agent-id="${esc(agentId)}"
32072
+ data-session-preview-status="${esc(
32073
+ previewState?.status || "loading"
32074
+ )}"
31898
32075
  >
31899
32076
  <button
31900
32077
  class="session-row-main"
@@ -31926,7 +32103,9 @@ Waiting for Claude login output…</pre
31926
32103
  const lastTouched = formatSessionRelativeTime(
31927
32104
  record.last_active_at || record.created_at
31928
32105
  );
31929
- const previewText = getSessionPreviewText({
32106
+ const previewState = getSessionListPreviewState(record.session_id);
32107
+ const previewText = getSessionListPreviewText({
32108
+ sessionId: record.session_id,
31930
32109
  lastMessagePreview: record.last_message_preview,
31931
32110
  runtime,
31932
32111
  model,
@@ -31937,6 +32116,9 @@ Waiting for Claude login output…</pre
31937
32116
  class="agent-entry-card session-list-row session-list-row-history"
31938
32117
  data-history-session-id="${esc(record.session_id)}"
31939
32118
  data-agent-id="${esc(record.agent_id)}"
32119
+ data-session-preview-status="${esc(
32120
+ previewState?.status || "loading"
32121
+ )}"
31940
32122
  >
31941
32123
  <button
31942
32124
  class="session-row-main"
@@ -41468,6 +41650,23 @@ Waiting for Claude login output…</pre
41468
41650
  : "/scheduler/schedules";
41469
41651
  const data = await api("GET", schedulerPath);
41470
41652
  const rows = Array.isArray(data.schedules) ? data.schedules : [];
41653
+ const timezoneReconciliation =
41654
+ data.timezone_reconciliation &&
41655
+ typeof data.timezone_reconciliation === "object"
41656
+ ? data.timezone_reconciliation
41657
+ : null;
41658
+ const timezoneReconciliationGeneration = Number(
41659
+ timezoneReconciliation?.project_index_generation || 0
41660
+ );
41661
+ const timezoneCorrectionCount = Number(
41662
+ timezoneReconciliation?.timezone_correction_count || 0
41663
+ );
41664
+ const showTimezoneCorrection =
41665
+ timezoneCorrectionCount > 0 &&
41666
+ Number.isInteger(timezoneReconciliationGeneration) &&
41667
+ timezoneReconciliationGeneration > 0 &&
41668
+ timezoneReconciliationGeneration !==
41669
+ hostSchedulerState.lastTimezoneReconciliationGeneration;
41471
41670
  const requestedId = sanitizeRouteText(opts.scheduleId);
41472
41671
  const shouldOpenSetup =
41473
41672
  opts.newDraft === true ||
@@ -41487,6 +41686,10 @@ Waiting for Claude login output…</pre
41487
41686
  : [],
41488
41687
  loaded: true,
41489
41688
  loading: false,
41689
+ lastTimezoneReconciliationGeneration:
41690
+ timezoneReconciliationGeneration > 0
41691
+ ? timezoneReconciliationGeneration
41692
+ : hostSchedulerState.lastTimezoneReconciliationGeneration,
41490
41693
  selectedId,
41491
41694
  editingId: shouldOpenSetup
41492
41695
  ? opts.newDraft === true
@@ -41501,6 +41704,11 @@ Waiting for Claude login output…</pre
41501
41704
  : hostSchedulerState.setupPageMode || "edit"
41502
41705
  : "list",
41503
41706
  error: "",
41707
+ status: showTimezoneCorrection
41708
+ ? `Normalized ${timezoneCorrectionCount} portable schedule timezone${
41709
+ timezoneCorrectionCount === 1 ? "" : "s"
41710
+ } to this Host.`
41711
+ : "",
41504
41712
  };
41505
41713
  } catch (err) {
41506
41714
  hostSchedulerState = {
@@ -41528,6 +41736,12 @@ Waiting for Claude login output…</pre
41528
41736
  {}
41529
41737
  );
41530
41738
  const rows = Array.isArray(data.schedules) ? data.schedules : [];
41739
+ const timezoneCorrectionCount = Number(
41740
+ data.timezone_correction_count || 0
41741
+ );
41742
+ const timezoneReconciliationGeneration = Number(
41743
+ data.timezone_normalization?.project_index_generation || 0
41744
+ );
41531
41745
  hostSchedulerState = {
41532
41746
  ...hostSchedulerState,
41533
41747
  rows,
@@ -41539,12 +41753,25 @@ Waiting for Claude login output…</pre
41539
41753
  loaded: true,
41540
41754
  refreshing: false,
41541
41755
  loading: false,
41756
+ lastTimezoneReconciliationGeneration:
41757
+ timezoneReconciliationGeneration > 0
41758
+ ? timezoneReconciliationGeneration
41759
+ : hostSchedulerState.lastTimezoneReconciliationGeneration,
41542
41760
  error: "",
41543
- status: `Refreshed ${Number(
41544
- data.project_count || 0
41545
- )} Browser Root project${
41546
- Number(data.project_count || 0) === 1 ? "" : "s"
41547
- }.`,
41761
+ status:
41762
+ timezoneCorrectionCount > 0
41763
+ ? `Refreshed ${Number(
41764
+ data.project_count || 0
41765
+ )} Browser Root project${
41766
+ Number(data.project_count || 0) === 1 ? "" : "s"
41767
+ }. Normalized ${timezoneCorrectionCount} portable schedule timezone${
41768
+ timezoneCorrectionCount === 1 ? "" : "s"
41769
+ } to this Host.`
41770
+ : `Refreshed ${Number(
41771
+ data.project_count || 0
41772
+ )} Browser Root project${
41773
+ Number(data.project_count || 0) === 1 ? "" : "s"
41774
+ }.`,
41548
41775
  };
41549
41776
  } catch (err) {
41550
41777
  hostSchedulerState = {
@@ -1,4 +1,4 @@
1
- import{g as ee,c as Y,r as K,j as Z}from"./index-Cuy-f6-d.js";var X={exports:{}};(function(e){var t=typeof window<"u"?window:typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope?self:{};/**
1
+ import{g as ee,c as Y,r as K,j as Z}from"./index-CqfVnL4A.js";var X={exports:{}};(function(e){var t=typeof window<"u"?window:typeof WorkerGlobalScope<"u"&&self instanceof WorkerGlobalScope?self:{};/**
2
2
  * Prism: Lightweight, robust, elegant syntax highlighting
3
3
  *
4
4
  * @license MIT <https://opensource.org/licenses/MIT>