oysterun 1.5.7-beta.2 → 1.5.7-beta.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 (118) hide show
  1. package/README.md +5 -3
  2. package/bin/oysterun.mjs +3 -0
  3. package/dev/client/connectivity/data-channel-adapter.mjs +146 -0
  4. package/dev/client/connectivity/host-connection-feedback.mjs +193 -0
  5. package/dev/client/connectivity/logical-host-connection.mjs +427 -0
  6. package/dev/client/connectivity/managed-host-browser.d.mts +50 -0
  7. package/dev/client/connectivity/managed-host-browser.mjs +1929 -0
  8. package/dev/client/connectivity/random-id.mjs +28 -0
  9. package/dev/client/connectivity/webrtc-frame.mjs +87 -0
  10. package/dev/client/connectivity/webrtc-logical-transport.mjs +576 -0
  11. package/dev/client/web/index.html +6230 -642
  12. package/dev/client/web-chat/dist/assets/{ReactPrism-pByE_IEa.js → ReactPrism-DXWjWRSc.js} +1 -1
  13. package/dev/client/web-chat/dist/assets/ReactPrism-DXWjWRSc.js.map +1 -0
  14. package/dev/client/web-chat/dist/assets/{index-AdSozXW7.js → index-6PAeuTVW.js} +2 -2
  15. package/dev/client/web-chat/dist/assets/index-6PAeuTVW.js.map +1 -0
  16. package/dev/client/web-chat/dist/assets/index-B9fWrEk-.js.map +1 -1
  17. package/dev/client/web-chat/dist/assets/{index-BNznPZkv.js → index-CMTuzF_U.js} +1 -1
  18. package/dev/client/web-chat/dist/assets/index-CMTuzF_U.js.map +1 -0
  19. package/dev/client/web-chat/dist/assets/{index-BFaINJYC.css → index-DyTV0z1z.css} +1 -1
  20. package/dev/client/web-chat/dist/assets/{index-BjpMgToA.js → index-c-mXm6Vw.js} +83 -83
  21. package/dev/client/web-chat/dist/assets/index-c-mXm6Vw.js.map +1 -0
  22. package/dev/client/web-chat/dist/assets/{pdf-cTY9JS8Q.js → pdf-DCR-4G9y.js} +3 -3
  23. package/dev/client/web-chat/dist/assets/pdf-DCR-4G9y.js.map +1 -0
  24. package/dev/client/web-chat/dist/assets/{web-CQIq3Kfx.js → web-dsaC3bqP.js} +1 -1
  25. package/dev/client/web-chat/dist/assets/web-dsaC3bqP.js.map +1 -0
  26. package/dev/client/web-chat/dist/assets/{web-CpdV1b9l.js → web-rNZ6ony7.js} +1 -1
  27. package/dev/client/web-chat/dist/assets/web-rNZ6ony7.js.map +1 -0
  28. package/dev/client/web-chat/dist/index.html +2 -2
  29. package/docs/routec/config_hierachy.md +21 -2
  30. package/docs/routec/dev/cloud_deplyments.md +228 -18
  31. package/docs/routec/dev/npm_native_dependency_install.md +33 -14
  32. package/docs/routec/dev/npmjs_publish.md +81 -15
  33. package/docs/routec/dev/provider_startup_diagnostics.md +20 -11
  34. package/docs/routec/form_UI_fields.md +42 -4
  35. package/docs/routec/host2_turn_started_idempotency_diagnostics.md +140 -0
  36. package/docs/routec/host_cloud_app_auth.md +5 -0
  37. package/docs/routec/host_setup.md +94 -64
  38. package/docs/routec/oysterun_operation_interface_spec.md +20 -0
  39. package/docs/routec/provider_contract.md +30 -10
  40. package/host-service/adapters/claude-code-adapter.mjs +194 -38
  41. package/host-service/adapters/codex-app-server-adapter.mjs +15 -0
  42. package/host-service/adapters/debug-fixture-provider-adapter.mjs +35 -4
  43. package/host-service/adapters/debug-large-tool-spillover-provider-adapter.mjs +35 -4
  44. package/host-service/adapters/debug-p135-codex-replay-provider-adapter.mjs +35 -4
  45. package/host-service/adapters/debug-routec-structural-replay-provider-adapter.mjs +27 -1
  46. package/host-service/assets/product-skills/Oysterun/modules/oysterun-sessions/SKILL.md +5 -1
  47. package/host-service/assets/product-skills/oysterun-sessions/SKILL.md +5 -1
  48. package/host-service/browser-root-project-index.mjs +63 -6
  49. package/host-service/cli/product-cli.mjs +53 -32
  50. package/host-service/config.mjs +132 -1
  51. package/host-service/connectivity/data-channel-adapter.mjs +1 -0
  52. package/host-service/connectivity/logical-host-connection.mjs +1 -0
  53. package/host-service/connectivity/managed-tunnel-runtime-status.mjs +101 -0
  54. package/host-service/connectivity/node-webrtc-runtime.mjs +181 -0
  55. package/host-service/connectivity/random-id.mjs +1 -0
  56. package/host-service/connectivity/webrtc-frame.mjs +1 -0
  57. package/host-service/connectivity/webrtc-host-dispatcher.mjs +171 -0
  58. package/host-service/connectivity/webrtc-logical-transport.mjs +1 -0
  59. package/host-service/connectivity/webrtc-signaling-service.mjs +1394 -0
  60. package/host-service/frp-agent.mjs +861 -409
  61. package/host-service/host-login-bootstrap-tokens.mjs +45 -0
  62. package/host-service/matrix-event-correlator.mjs +199 -7
  63. package/host-service/matrix-facade.mjs +454 -59
  64. package/host-service/matrix-read-authority.mjs +48 -44
  65. package/host-service/matrix-transcript-read-adapter.mjs +22 -39
  66. package/host-service/package.json +17 -4
  67. package/host-service/provider-registry.mjs +10 -3
  68. package/host-service/provider-resume-contract.mjs +95 -0
  69. package/host-service/provider-startup-preflight.mjs +60 -15
  70. package/host-service/routec-host2-intake-diagnostics.mjs +146 -0
  71. package/host-service/routec-matrix-media-store.mjs +91 -3
  72. package/host-service/routec-matrix-sqlite-store.mjs +561 -149
  73. package/host-service/routec-matrix-storage-adapter.mjs +305 -371
  74. package/host-service/routec-matrix-stream-seq-repair.mjs +554 -0
  75. package/host-service/scheduler-service.mjs +68 -15
  76. package/host-service/server.mjs +975 -164
  77. package/host-service/session-history.mjs +2 -0
  78. package/host-service/session-lifecycle-snapshot.mjs +162 -0
  79. package/host-service/session-loop-definitions.mjs +1605 -112
  80. package/host-service/session-manager.mjs +271 -71
  81. package/host-service/setup.mjs +611 -105
  82. package/host-service/telegram-adapter.mjs +267 -0
  83. package/host-service/telegram-bridge.mjs +1061 -15
  84. package/host-service/telegram-media.mjs +319 -0
  85. package/package.json +6 -3
  86. package/static/site/assets/site.css +1213 -2
  87. package/static/site/content/AGENTS.md +150 -0
  88. package/static/site/content/blog/drafts/20260718-24hr-agent-team/assets/article.css +52 -0
  89. package/static/site/content/blog/drafts/20260718-24hr-agent-team/content.json +16 -0
  90. package/static/site/content/blog/drafts/20260718-24hr-agent-team/zh/index.html +35 -0
  91. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/audio/A1.wav +0 -0
  92. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/audio/A4.wav +0 -0
  93. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/audio/B3.wav +0 -0
  94. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/audio/C2.wav +0 -0
  95. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/audio/D3.wav +0 -0
  96. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/audio/G3.wav +0 -0
  97. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/oysterun_logo_v5.PNG +0 -0
  98. package/static/site/content/blog/drafts/voice-input-model-experiment/assets/site.css +2436 -0
  99. package/static/site/content/blog/drafts/voice-input-model-experiment/content.json +21 -0
  100. package/static/site/content/blog/drafts/voice-input-model-experiment/en/index.html +820 -0
  101. package/static/site/content/blog/drafts/voice-input-model-experiment/zh/index.html +1665 -0
  102. package/static/site/content/tutorials/drafts/getting-started/assets/AppStore.png +0 -0
  103. package/static/site/content/tutorials/drafts/getting-started/assets/article.css +66 -0
  104. package/static/site/content/tutorials/drafts/getting-started/assets/chat.png +0 -0
  105. package/static/site/content/tutorials/drafts/getting-started/content.json +16 -0
  106. package/static/site/content/tutorials/drafts/getting-started/en/index.html +161 -0
  107. package/tool_scripts/oysterun_npm_package_manifest.mjs +88 -0
  108. package/tool_scripts/oysterun_release_receipt_lib.mjs +47 -0
  109. package/tool_scripts/verify_oysterun_package_layout.mjs +172 -0
  110. package/tool_scripts/verify_oysterun_release_receipt.mjs +96 -0
  111. package/tool_scripts/verify_oysterun_runtime.mjs +348 -8
  112. package/dev/client/web-chat/dist/assets/ReactPrism-pByE_IEa.js.map +0 -1
  113. package/dev/client/web-chat/dist/assets/index-AdSozXW7.js.map +0 -1
  114. package/dev/client/web-chat/dist/assets/index-BNznPZkv.js.map +0 -1
  115. package/dev/client/web-chat/dist/assets/index-BjpMgToA.js.map +0 -1
  116. package/dev/client/web-chat/dist/assets/pdf-cTY9JS8Q.js.map +0 -1
  117. package/dev/client/web-chat/dist/assets/web-CQIq3Kfx.js.map +0 -1
  118. package/dev/client/web-chat/dist/assets/web-CpdV1b9l.js.map +0 -1
package/README.md CHANGED
@@ -248,8 +248,9 @@ npm start
248
248
  Useful commands:
249
249
 
250
250
  ```bash
251
- npm run setup # first-run direct Host setup
252
- npm run setup -- --enable-cloud # optional Cloud registration
251
+ npm run setup # first-run Managed Tunnel Host setup
252
+ node host-service/setup.mjs # source-local Direct Host setup
253
+ node bin/oysterun.mjs setup --local # explicit local-only Direct setup
253
254
  npm start # plain start, no watch mode
254
255
  npm run dev # watch mode for Host JS files
255
256
  npm run cli # local CLI client
@@ -272,7 +273,8 @@ Port notes:
272
273
  - direct mode can also store an optional `public_base_url` there for manual client connection
273
274
  - fresh Session Setup runtime defaults, including interface style plus Claude/Codex provider defaults, are stored there under `session_defaults`
274
275
  - Host Preferences shows the active config path, so `OYSTERUN_CONFIG_DIR` overrides are visible in the UI
275
- - Host now defaults to direct Host mode unless Cloud registration is explicitly enabled
276
+ - release setup defaults to Oysterun Managed Tunnel
277
+ - source-local setup and `oysterun setup --local` retain explicit Direct mode
276
278
 
277
279
  ### Optional: start backend + Host together for repo development
278
280
 
package/bin/oysterun.mjs CHANGED
@@ -191,6 +191,9 @@ Product modules:
191
191
 
192
192
  First run:
193
193
  If Host config is missing or incomplete, 'oysterun' enters setup.
194
+ Release setup uses Oysterun Managed Tunnel by default.
195
+ Use 'oysterun setup --local' for local-only Direct Host setup.
196
+ Use '--enable-cloud-direct' only for advanced Direct Cloud registration.
194
197
 
195
198
  Install contract:
196
199
  npm install -g oysterun installs files only. It does not run setup or start
@@ -0,0 +1,146 @@
1
+ function toUint8Array(value) {
2
+ if (value instanceof Uint8Array) return value;
3
+ if (value instanceof ArrayBuffer) return new Uint8Array(value);
4
+ if (globalThis.Buffer?.isBuffer(value)) {
5
+ return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
6
+ }
7
+ throw new Error("DataChannel message must be binary");
8
+ }
9
+
10
+ export function adaptBrowserDataChannel(channel) {
11
+ const messageListeners = new Set();
12
+ const openListeners = new Set();
13
+ const bufferedLowListeners = new Set();
14
+ const closeListeners = new Set();
15
+ const errorListeners = new Set();
16
+ channel.binaryType = "arraybuffer";
17
+
18
+ channel.addEventListener("message", (event) => {
19
+ try {
20
+ const bytes = toUint8Array(event.data);
21
+ for (const listener of messageListeners) listener(bytes);
22
+ } catch (error) {
23
+ for (const listener of errorListeners) listener(error);
24
+ }
25
+ });
26
+ channel.addEventListener("open", () => {
27
+ for (const listener of openListeners) listener();
28
+ });
29
+ channel.addEventListener("bufferedamountlow", () => {
30
+ for (const listener of bufferedLowListeners) listener();
31
+ });
32
+ channel.addEventListener("close", () => {
33
+ for (const listener of closeListeners) listener();
34
+ });
35
+ channel.addEventListener("error", (event) => {
36
+ const error = new Error(event?.error?.message || "Browser DataChannel error");
37
+ for (const listener of errorListeners) listener(error);
38
+ });
39
+
40
+ return {
41
+ label: channel.label,
42
+ isOpen: () => channel.readyState === "open",
43
+ bufferedAmount: () => channel.bufferedAmount,
44
+ setBufferedAmountLowThreshold(bytes) {
45
+ channel.bufferedAmountLowThreshold = bytes;
46
+ },
47
+ sendBinary(bytes) {
48
+ channel.send(bytes);
49
+ return { buffered: channel.bufferedAmount > 0 };
50
+ },
51
+ onMessage(listener) {
52
+ messageListeners.add(listener);
53
+ return () => messageListeners.delete(listener);
54
+ },
55
+ onOpen(listener) {
56
+ openListeners.add(listener);
57
+ return () => openListeners.delete(listener);
58
+ },
59
+ onBufferedAmountLow(listener) {
60
+ bufferedLowListeners.add(listener);
61
+ return () => bufferedLowListeners.delete(listener);
62
+ },
63
+ onClose(listener) {
64
+ closeListeners.add(listener);
65
+ return () => closeListeners.delete(listener);
66
+ },
67
+ onError(listener) {
68
+ errorListeners.add(listener);
69
+ return () => errorListeners.delete(listener);
70
+ },
71
+ close() {
72
+ channel.close();
73
+ },
74
+ };
75
+ }
76
+
77
+ export function adaptNodeDataChannel(channel) {
78
+ const messageListeners = new Set();
79
+ const openListeners = new Set();
80
+ const bufferedLowListeners = new Set();
81
+ const closeListeners = new Set();
82
+ const errorListeners = new Set();
83
+
84
+ channel.onMessage((message) => {
85
+ if (typeof message === "string") {
86
+ for (const listener of errorListeners) {
87
+ listener(new Error("Oysterun transport rejects text DataChannel frames"));
88
+ }
89
+ return;
90
+ }
91
+ const bytes = toUint8Array(message);
92
+ for (const listener of messageListeners) listener(bytes);
93
+ });
94
+ channel.onOpen(() => {
95
+ for (const listener of openListeners) listener();
96
+ });
97
+ channel.onBufferedAmountLow(() => {
98
+ for (const listener of bufferedLowListeners) listener();
99
+ });
100
+ channel.onClosed(() => {
101
+ for (const listener of closeListeners) listener();
102
+ });
103
+ channel.onError((message) => {
104
+ const error = new Error(String(message));
105
+ for (const listener of errorListeners) listener(error);
106
+ });
107
+
108
+ return {
109
+ label: channel.getLabel(),
110
+ isOpen: () => channel.isOpen(),
111
+ bufferedAmount: () => channel.bufferedAmount(),
112
+ setBufferedAmountLowThreshold: (bytes) =>
113
+ channel.setBufferedAmountLowThreshold(bytes),
114
+ sendBinary(bytes) {
115
+ // False means the message was accepted into the native send buffer.
116
+ // Retrying on false duplicates the frame.
117
+ return {
118
+ buffered:
119
+ channel.sendMessageBinary(globalThis.Buffer.from(bytes)) === false,
120
+ };
121
+ },
122
+ onMessage(listener) {
123
+ messageListeners.add(listener);
124
+ return () => messageListeners.delete(listener);
125
+ },
126
+ onOpen(listener) {
127
+ openListeners.add(listener);
128
+ return () => openListeners.delete(listener);
129
+ },
130
+ onBufferedAmountLow(listener) {
131
+ bufferedLowListeners.add(listener);
132
+ return () => bufferedLowListeners.delete(listener);
133
+ },
134
+ onClose(listener) {
135
+ closeListeners.add(listener);
136
+ return () => closeListeners.delete(listener);
137
+ },
138
+ onError(listener) {
139
+ errorListeners.add(listener);
140
+ return () => errorListeners.delete(listener);
141
+ },
142
+ close() {
143
+ channel.close();
144
+ },
145
+ };
146
+ }
@@ -0,0 +1,193 @@
1
+ export const HOST_CONNECTION_RECONNECT_WARNING_DELAY_MS = 1_500;
2
+ export const HOST_CONNECTION_LOST_DELAY_MS = 30_000;
3
+ export const PAGE_REFRESH_PENDING_RESET_MS = 1_500;
4
+
5
+ function boundedString(value) {
6
+ return typeof value === "string" ? value.trim() : "";
7
+ }
8
+
9
+ export function normalizeHostConnectionStatus(status = {}) {
10
+ const originKind =
11
+ status.origin_kind === "managed_endpoint"
12
+ ? "managed_endpoint"
13
+ : "direct_host";
14
+ const activePathKind = boundedString(status.active_path_kind);
15
+ const unavailable =
16
+ status.closed === true ||
17
+ status.browser_online === false ||
18
+ status.managed_host_unavailable === true ||
19
+ status.last_request_failed === true ||
20
+ ["closed", "disconnected", "recovering", "unavailable"].includes(
21
+ status.connection_state
22
+ );
23
+ let qualifier = "Direct Host";
24
+ if (originKind === "managed_endpoint") {
25
+ qualifier =
26
+ activePathKind === "webrtc" ? "Managed WebRTC" : "Managed Endpoint";
27
+ }
28
+ return {
29
+ connected: !unavailable,
30
+ qualifier,
31
+ origin_kind: originKind,
32
+ active_path_kind: activePathKind || null,
33
+ };
34
+ }
35
+
36
+ export function connectionFeedbackPresentation({
37
+ signal,
38
+ disconnectedAt = null,
39
+ now = Date.now(),
40
+ } = {}) {
41
+ if (!signal || signal.connected === true) {
42
+ return {
43
+ visible: true,
44
+ state: "connected",
45
+ label: "Connected",
46
+ qualifier: signal?.qualifier || "",
47
+ };
48
+ }
49
+ const elapsedMs = Math.max(0, now - (disconnectedAt ?? now));
50
+ if (elapsedMs < HOST_CONNECTION_RECONNECT_WARNING_DELAY_MS) {
51
+ return {
52
+ visible: false,
53
+ state: "transient",
54
+ label: "",
55
+ qualifier: signal.qualifier,
56
+ };
57
+ }
58
+ if (elapsedMs < HOST_CONNECTION_LOST_DELAY_MS) {
59
+ return {
60
+ visible: true,
61
+ state: "reconnecting",
62
+ label: "Reconnecting...",
63
+ qualifier: signal.qualifier,
64
+ };
65
+ }
66
+ return {
67
+ visible: true,
68
+ state: "lost",
69
+ label: "Connection Lost. Still retrying.",
70
+ qualifier: signal.qualifier,
71
+ };
72
+ }
73
+
74
+ function presentationKey(presentation) {
75
+ return JSON.stringify([
76
+ presentation.visible,
77
+ presentation.state,
78
+ presentation.label,
79
+ presentation.qualifier,
80
+ ]);
81
+ }
82
+
83
+ export function createHostConnectionFeedbackController({
84
+ onPresentation,
85
+ now = () => Date.now(),
86
+ setTimeoutImpl = globalThis.setTimeout.bind(globalThis),
87
+ clearTimeoutImpl = globalThis.clearTimeout.bind(globalThis),
88
+ } = {}) {
89
+ if (typeof onPresentation !== "function") {
90
+ throw new Error("Host connection feedback listener is required");
91
+ }
92
+ let signal = null;
93
+ let disconnectedAt = null;
94
+ let warningTimer = null;
95
+ let lostTimer = null;
96
+ let lastPresentationKey = "";
97
+
98
+ const emit = () => {
99
+ const presentation = connectionFeedbackPresentation({
100
+ signal,
101
+ disconnectedAt,
102
+ now: now(),
103
+ });
104
+ const nextKey = presentationKey(presentation);
105
+ if (nextKey === lastPresentationKey) return;
106
+ lastPresentationKey = nextKey;
107
+ onPresentation(presentation);
108
+ };
109
+
110
+ const clearTimers = () => {
111
+ if (warningTimer !== null) clearTimeoutImpl(warningTimer);
112
+ if (lostTimer !== null) clearTimeoutImpl(lostTimer);
113
+ warningTimer = null;
114
+ lostTimer = null;
115
+ };
116
+
117
+ const scheduleThresholds = () => {
118
+ warningTimer = setTimeoutImpl(() => {
119
+ warningTimer = null;
120
+ emit();
121
+ }, HOST_CONNECTION_RECONNECT_WARNING_DELAY_MS);
122
+ lostTimer = setTimeoutImpl(() => {
123
+ lostTimer = null;
124
+ emit();
125
+ }, HOST_CONNECTION_LOST_DELAY_MS);
126
+ };
127
+
128
+ return {
129
+ handleStatus(status) {
130
+ const nextSignal = normalizeHostConnectionStatus(status);
131
+ const wasConnected = signal?.connected !== false;
132
+ signal = nextSignal;
133
+ if (nextSignal.connected) {
134
+ disconnectedAt = null;
135
+ clearTimers();
136
+ emit();
137
+ return;
138
+ }
139
+ if (wasConnected || disconnectedAt === null) {
140
+ disconnectedAt = now();
141
+ clearTimers();
142
+ scheduleThresholds();
143
+ }
144
+ emit();
145
+ },
146
+ render() {
147
+ emit();
148
+ },
149
+ close() {
150
+ clearTimers();
151
+ },
152
+ };
153
+ }
154
+
155
+ export function createPageRefreshController({
156
+ reload,
157
+ onPendingChange = () => {},
158
+ setTimeoutImpl = globalThis.setTimeout.bind(globalThis),
159
+ clearTimeoutImpl = globalThis.clearTimeout.bind(globalThis),
160
+ pendingResetMs = PAGE_REFRESH_PENDING_RESET_MS,
161
+ } = {}) {
162
+ if (typeof reload !== "function") {
163
+ throw new Error("Page reload callback is required");
164
+ }
165
+ let pending = false;
166
+ let resetTimer = null;
167
+
168
+ const setPending = (nextPending) => {
169
+ pending = nextPending;
170
+ onPendingChange(pending);
171
+ };
172
+
173
+ return {
174
+ request() {
175
+ if (pending) return false;
176
+ setPending(true);
177
+ resetTimer = setTimeoutImpl(() => {
178
+ resetTimer = null;
179
+ setPending(false);
180
+ }, pendingResetMs);
181
+ reload();
182
+ return true;
183
+ },
184
+ isPending() {
185
+ return pending;
186
+ },
187
+ close() {
188
+ if (resetTimer !== null) clearTimeoutImpl(resetTimer);
189
+ resetTimer = null;
190
+ if (pending) setPending(false);
191
+ },
192
+ };
193
+ }