oysterun 1.6.0-beta.2 → 1.6.0-beta.20

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 (243) hide show
  1. package/bin/oysterun.mjs +64 -17
  2. package/dev/client/connectivity/completed-message-neighbor-click.mjs +78 -0
  3. package/dev/client/connectivity/completed-message-neighbor-contract.mjs +205 -0
  4. package/dev/client/connectivity/completed-message-visibility.d.mts +4 -4
  5. package/dev/client/connectivity/completed-message-visibility.mjs +14 -11
  6. package/dev/client/connectivity/desktop-chat-sidebar-preference.mjs +57 -0
  7. package/dev/client/connectivity/managed-host-browser.mjs +10 -0
  8. package/dev/client/connectivity/session-navigation-intent.mjs +189 -0
  9. package/dev/client/connectivity/session-transition-state.mjs +18 -6
  10. package/dev/client/connectivity/test-completed-message-neighbor-click.mjs +180 -0
  11. package/dev/client/web/index.html +5109 -546
  12. package/dev/client/web-chat/dist/assets/{ReactPrism-BwJhidYf.js → ReactPrism-ClyFITK3.js} +1 -1
  13. package/dev/client/web-chat/dist/assets/ReactPrism-ClyFITK3.js.map +1 -0
  14. package/dev/client/web-chat/dist/assets/SessionsSetupApp-CblAxkYh.css +1 -0
  15. package/dev/client/web-chat/dist/assets/SessionsSetupApp-DjPZ5ktj.js +6442 -0
  16. package/dev/client/web-chat/dist/assets/SessionsSetupApp-DjPZ5ktj.js.map +1 -0
  17. package/dev/client/web-chat/dist/assets/index-B9fWrEk-.js.map +1 -1
  18. package/dev/client/web-chat/dist/assets/index-Bg19BOiF.js +9 -0
  19. package/dev/client/web-chat/dist/assets/index-Bg19BOiF.js.map +1 -0
  20. package/dev/client/web-chat/dist/assets/{index--1xmQ99x.js → index-CH6MWK2I.js} +2 -2
  21. package/dev/client/web-chat/dist/assets/index-CH6MWK2I.js.map +1 -0
  22. package/dev/client/web-chat/dist/assets/index-L3a5OiDt.js +2 -0
  23. package/dev/client/web-chat/dist/assets/index-L3a5OiDt.js.map +1 -0
  24. package/dev/client/web-chat/dist/assets/matrix-entry-CbU0Ygxr.js +594 -0
  25. package/dev/client/web-chat/dist/assets/matrix-entry-CbU0Ygxr.js.map +1 -0
  26. package/dev/client/web-chat/dist/assets/matrix-entry-W9GaZpsV.css +10 -0
  27. package/dev/client/web-chat/dist/assets/{pdf-ZjtalfgI.js → pdf-CxPJMrHN.js} +5 -5
  28. package/dev/client/web-chat/dist/assets/pdf-CxPJMrHN.js.map +1 -0
  29. package/dev/client/web-chat/dist/assets/session-action-menu-contract-DcS82CRX.css +1 -0
  30. package/dev/client/web-chat/dist/assets/web-9H0iDFny.js +1 -0
  31. package/dev/client/web-chat/dist/assets/web-9H0iDFny.js.map +1 -0
  32. package/dev/client/web-chat/dist/assets/web-DAe240E6.js +1 -0
  33. package/dev/client/web-chat/dist/assets/web-DAe240E6.js.map +1 -0
  34. package/dev/client/web-chat/dist/index.html +1 -2
  35. package/docs/routec/dev/cloud_deplyments.md +5 -0
  36. package/docs/routec/dev/npm_native_dependency_install.md +6 -2
  37. package/docs/routec/dev/npmjs_publish.md +55 -19
  38. package/docs/routec/dev/p262_setup_input_file.md +117 -0
  39. package/docs/routec/host2_turn_started_idempotency_diagnostics.md +8 -7
  40. package/docs/routec/host_setup.md +39 -10
  41. package/docs/routec/oysterun_operation_interface_spec.md +78 -2
  42. package/docs/routec/provider_contract.md +35 -5
  43. package/host-service/README.md +36 -0
  44. package/host-service/adapters/claude-code-adapter.mjs +48 -5
  45. package/host-service/adapters/codex-app-server-adapter.mjs +320 -18
  46. package/host-service/adapters/debug-fixture-provider-adapter.mjs +102 -1
  47. package/host-service/adapters/debug-large-tool-spillover-provider-adapter.mjs +2 -0
  48. package/host-service/adapters/debug-p135-codex-replay-provider-adapter.mjs +2 -0
  49. package/host-service/adapters/debug-routec-structural-replay-provider-adapter.mjs +2 -0
  50. package/host-service/agent-config.mjs +16 -5
  51. package/host-service/apns-complete-message-dispatcher.mjs +1 -0
  52. package/host-service/assets/product-skills/Oysterun/modules/oysterun-find-context/SKILL.md +24 -0
  53. package/host-service/assets/product-skills/Oysterun/modules/oysterun-session-chat/SKILL.md +66 -5
  54. package/host-service/assets/product-skills/Oysterun/modules/oysterun-sessions/SKILL.md +81 -0
  55. package/host-service/assets/product-skills/Oysterun/modules/oysterun-website/SKILL.md +7 -0
  56. package/host-service/assets/product-skills/oysterun-find-context/SKILL.md +24 -0
  57. package/host-service/assets/product-skills/oysterun-session-chat/SKILL.md +66 -5
  58. package/host-service/assets/product-skills/oysterun-sessions/SKILL.md +81 -0
  59. package/host-service/assets/product-skills/oysterun-website/SKILL.md +7 -0
  60. package/host-service/callback-durable-store.mjs +354 -47
  61. package/host-service/callback-foundation.mjs +21 -1
  62. package/host-service/callback-host-integration.mjs +345 -30
  63. package/host-service/claude-acp-model-discovery.mjs +23 -18
  64. package/host-service/cli/assert-installer-reinstall-target.mjs +9 -0
  65. package/host-service/cli/installer-recovery-guidance.mjs +20 -0
  66. package/host-service/cli/prepare-restart-restore.mjs +2 -0
  67. package/host-service/cli/product-cli.mjs +34 -33
  68. package/host-service/cli/reconcile-initial-preflight.mjs +19 -0
  69. package/host-service/cli/recover-installer-lifecycle.mjs +24 -0
  70. package/host-service/cli/run-host-lifecycle.mjs +2 -0
  71. package/host-service/cli/run-staged-lifecycle-recovery.mjs +161 -0
  72. package/host-service/codex-model-discovery.mjs +23 -18
  73. package/host-service/complete-cli-uninstall.mjs +176 -0
  74. package/host-service/complete-message-notification-predicate.mjs +8 -6
  75. package/host-service/complete-message-notification-title.mjs +4 -0
  76. package/host-service/completed-session-unread.mjs +425 -42
  77. package/host-service/completed-turn-navigation.mjs +444 -144
  78. package/host-service/completed-turn-notification-observer.mjs +116 -0
  79. package/host-service/completed-turn-service.mjs +445 -0
  80. package/host-service/config.mjs +115 -9
  81. package/host-service/desired-open-session-state.mjs +331 -0
  82. package/host-service/fixtures/p218-completed-turn-notification.json +11 -0
  83. package/host-service/frp-agent.mjs +9 -9
  84. package/host-service/grok-acp-model-discovery.mjs +31 -33
  85. package/host-service/host-lifecycle-recovery.mjs +37 -0
  86. package/host-service/host-port-resolution.mjs +98 -0
  87. package/host-service/host-runtime-diagnostics.mjs +18 -1
  88. package/host-service/initial-preflight-recovery.mjs +71 -0
  89. package/host-service/installer-recovery-guidance.mjs +85 -0
  90. package/host-service/installer-reinstall-bindings.mjs +49 -0
  91. package/host-service/installer-reinstall-target.mjs +49 -0
  92. package/host-service/legacy-continuity-observation.mjs +33 -0
  93. package/host-service/lifecycle-compat-cli.mjs +13 -0
  94. package/host-service/lifecycle-compat-preload.mjs +89 -0
  95. package/host-service/lifecycle-compatibility.mjs +164 -0
  96. package/host-service/lifecycle-continuity-assets.mjs +222 -0
  97. package/host-service/lifecycle-continuity-probe.mjs +97 -0
  98. package/host-service/lifecycle-continuity-projection.mjs +29 -0
  99. package/host-service/lifecycle-continuity-runner.mjs +192 -0
  100. package/host-service/lifecycle-continuity.mjs +493 -0
  101. package/host-service/lifecycle-service-render-bundle.mjs +120 -0
  102. package/host-service/lifecycle-user-path.mjs +34 -0
  103. package/host-service/matrix-credential-issuer.mjs +31 -1
  104. package/host-service/matrix-event-correlator.mjs +166 -27
  105. package/host-service/matrix-facade.mjs +71 -18
  106. package/host-service/matrix-room-binding.mjs +24 -0
  107. package/host-service/matrix-transcript-read-adapter.mjs +17 -3
  108. package/host-service/model-discovery-guardian.mjs +40 -0
  109. package/host-service/model-discovery-process.mjs +141 -0
  110. package/host-service/onboard-lifecycle-status.mjs +28 -0
  111. package/host-service/onboard.mjs +479 -0
  112. package/host-service/package.json +2 -1
  113. package/host-service/portable-scheduler-definitions.mjs +28 -0
  114. package/host-service/product-runtime-host-origin.mjs +22 -0
  115. package/host-service/provider-model-params.mjs +14 -17
  116. package/host-service/provider-model-refresh-runner.mjs +124 -169
  117. package/host-service/provider-model-refresh-schedule.mjs +11 -0
  118. package/host-service/provider-resume-contract.mjs +9 -3
  119. package/host-service/restart-restore-state.mjs +25 -0
  120. package/host-service/retained-host-state-holder.mjs +57 -0
  121. package/host-service/routec-matrix-sqlite-store.mjs +155 -10
  122. package/host-service/routec-matrix-storage-adapter.mjs +207 -25
  123. package/host-service/routec-matrix-storage-paths.mjs +12 -0
  124. package/host-service/routec-request-delay-window.mjs +55 -0
  125. package/host-service/scheduler-acp-provider-runner.mjs +2 -1
  126. package/host-service/scheduler-direct-provider-runner.mjs +71 -9
  127. package/host-service/scheduler-run-timeout-policy.mjs +85 -0
  128. package/host-service/scheduler-service.mjs +96 -9
  129. package/host-service/scheduler-store.mjs +9 -2
  130. package/host-service/server.mjs +2150 -536
  131. package/host-service/service-definition-migration.mjs +10 -3
  132. package/host-service/session-collection-service.mjs +320 -0
  133. package/host-service/session-history.mjs +99 -15
  134. package/host-service/session-list-preview-service.mjs +87 -12
  135. package/host-service/session-list-projection-service.mjs +182 -0
  136. package/host-service/session-loop-definitions.mjs +702 -75
  137. package/host-service/session-manager.mjs +473 -38
  138. package/host-service/session-switcher-projection.mjs +11 -3
  139. package/host-service/session-tags.mjs +451 -0
  140. package/host-service/session-transcript-target.mjs +103 -0
  141. package/host-service/setup-input-file.mjs +91 -0
  142. package/host-service/setup-provider-model-defaults.mjs +53 -0
  143. package/host-service/setup.mjs +133 -101
  144. package/host-service/stopped-user-host-package.mjs +60 -0
  145. package/host-service/test-fixtures/p236-navigation-original.mjs +500 -0
  146. package/host-service/update-reminder-state.mjs +7 -0
  147. package/package.json +1 -1
  148. package/static/oysterun-social-preview.png +0 -0
  149. package/static/site/assets/oysterun-social-preview.png +0 -0
  150. package/static/site/blog/en/index.html +17 -0
  151. package/static/site/blog/generated_manifest.json +9 -9
  152. package/static/site/blog/index.html +18 -0
  153. package/static/site/blog/voice-input-model-experiment/en/index.html +17 -0
  154. package/static/site/blog/voice-input-model-experiment/zh/index.html +17 -0
  155. package/static/site/blog/zh/index.html +17 -0
  156. package/static/site/docs/choose-and-recover-a-provider/en/index.html +17 -0
  157. package/static/site/docs/choose-and-recover-a-provider/zh/index.html +17 -0
  158. package/static/site/docs/configure-session-files-and-settings/en/index.html +17 -0
  159. package/static/site/docs/configure-session-files-and-settings/zh/index.html +17 -0
  160. package/static/site/docs/en/index.html +17 -0
  161. package/static/site/docs/generated_manifest.json +60 -60
  162. package/static/site/docs/host-authentication-and-connection/en/index.html +17 -0
  163. package/static/site/docs/host-authentication-and-connection/zh/index.html +17 -0
  164. package/static/site/docs/index.html +18 -0
  165. package/static/site/docs/install-and-use-a-skill/en/index.html +17 -0
  166. package/static/site/docs/install-and-use-a-skill/zh/index.html +17 -0
  167. package/static/site/docs/read-large-tool-results/en/index.html +17 -0
  168. package/static/site/docs/read-large-tool-results/zh/index.html +17 -0
  169. package/static/site/docs/recover-from-a-host-runtime-error/en/index.html +17 -0
  170. package/static/site/docs/recover-from-a-host-runtime-error/zh/index.html +17 -0
  171. package/static/site/docs/restore-work-after-a-host-restart/en/index.html +17 -0
  172. package/static/site/docs/restore-work-after-a-host-restart/zh/index.html +17 -0
  173. package/static/site/docs/send-images-and-media/en/index.html +17 -0
  174. package/static/site/docs/send-images-and-media/zh/index.html +17 -0
  175. package/static/site/docs/set-up-and-understand-a-host/en/index.html +17 -0
  176. package/static/site/docs/set-up-and-understand-a-host/zh/index.html +17 -0
  177. package/static/site/docs/understand-agent-activity/en/index.html +19 -2
  178. package/static/site/docs/understand-agent-activity/en/index.md +2 -2
  179. package/static/site/docs/understand-agent-activity/zh/index.html +19 -2
  180. package/static/site/docs/understand-agent-activity/zh/index.md +2 -2
  181. package/static/site/docs/use-the-oysterun-cli-safely/en/index.html +17 -0
  182. package/static/site/docs/use-the-oysterun-cli-safely/zh/index.html +17 -0
  183. package/static/site/docs/zh/index.html +17 -0
  184. package/static/site/en/index.html +18 -1
  185. package/static/site/en/index.md +1 -1
  186. package/static/site/faq/en/index.html +17 -0
  187. package/static/site/faq/index.html +18 -0
  188. package/static/site/faq/zh/index.html +17 -0
  189. package/static/site/getting-started/en/index.html +23 -1
  190. package/static/site/getting-started/en/index.md +7 -1
  191. package/static/site/getting-started/index.html +18 -0
  192. package/static/site/getting-started/zh/index.html +23 -1
  193. package/static/site/getting-started/zh/index.md +7 -1
  194. package/static/site/index.html +18 -0
  195. package/static/site/install.sh +676 -127
  196. package/static/site/overview/en/index.html +18 -1
  197. package/static/site/overview/en/index.md +2 -2
  198. package/static/site/overview/index.html +18 -0
  199. package/static/site/overview/zh/index.html +18 -1
  200. package/static/site/overview/zh/index.md +2 -2
  201. package/static/site/privacy/en/index.html +17 -0
  202. package/static/site/privacy/index.html +18 -0
  203. package/static/site/privacy/zh/index.html +17 -0
  204. package/static/site/zh/index.html +18 -1
  205. package/static/site/zh/index.md +1 -1
  206. package/static/site_source/content/tutorials/published/understand-agent-activity/en/article.html +2 -2
  207. package/static/site_source/content/tutorials/published/understand-agent-activity/zh/article.html +2 -2
  208. package/static/site_source/downloads/install.sh +676 -127
  209. package/static/site_source/manifests/host_install.json +2 -10
  210. package/static/site_source/pages/getting-started/en.html +6 -1
  211. package/static/site_source/pages/getting-started/zh.html +6 -1
  212. package/static/site_source/pages/home/en.html +1 -1
  213. package/static/site_source/pages/home/zh.html +1 -1
  214. package/static/site_source/pages/home_main.html +3 -2
  215. package/static/site_source/pages/overview/en.html +1 -1
  216. package/static/site_source/pages/overview/zh.html +1 -1
  217. package/tool_scripts/install_oysterun_update.mjs +78 -13
  218. package/tool_scripts/oysterun_continuity_transaction.mjs +438 -0
  219. package/tool_scripts/oysterun_npm_package_manifest.mjs +32 -0
  220. package/tool_scripts/oysterun_release_service.sh +5 -0
  221. package/tool_scripts/oysterun_service_common.sh +59 -24
  222. package/tool_scripts/oysterun_setup_input.sh +97 -0
  223. package/tool_scripts/oysterun_update_restart_runner.mjs +19 -3
  224. package/tool_scripts/p233_legacy_bootstrap.mjs +254 -0
  225. package/tool_scripts/resolve_user_host_port.mjs +15 -0
  226. package/tool_scripts/restart_oysterun.sh +8 -2
  227. package/tool_scripts/stack_dashboard_credentials.mjs +2 -1
  228. package/tool_scripts/status_oysterun_launch_agents.sh +13 -1
  229. package/tool_scripts/sync_host_stack_config.mjs +9 -6
  230. package/tool_scripts/test_environment_remote_cloud_deprovision.mjs +419 -0
  231. package/tool_scripts/uninstall_oysterun_launch_agents.sh +3 -5
  232. package/dev/client/web-chat/dist/assets/ReactPrism-BwJhidYf.js.map +0 -1
  233. package/dev/client/web-chat/dist/assets/index--1xmQ99x.js.map +0 -1
  234. package/dev/client/web-chat/dist/assets/index-Ce9NZBZf.js +0 -129
  235. package/dev/client/web-chat/dist/assets/index-Ce9NZBZf.js.map +0 -1
  236. package/dev/client/web-chat/dist/assets/index-DzIClZ3T.js +0 -2
  237. package/dev/client/web-chat/dist/assets/index-DzIClZ3T.js.map +0 -1
  238. package/dev/client/web-chat/dist/assets/index-NYnaSGBb.css +0 -1
  239. package/dev/client/web-chat/dist/assets/pdf-ZjtalfgI.js.map +0 -1
  240. package/dev/client/web-chat/dist/assets/web-BzHcZK2M.js +0 -1
  241. package/dev/client/web-chat/dist/assets/web-BzHcZK2M.js.map +0 -1
  242. package/dev/client/web-chat/dist/assets/web-c70YaskK.js +0 -1
  243. package/dev/client/web-chat/dist/assets/web-c70YaskK.js.map +0 -1
package/bin/oysterun.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- import { existsSync, readFileSync, rmSync } from "node:fs";
3
+ import { existsSync, readFileSync } from "node:fs";
4
4
  import { spawnSync } from "node:child_process";
5
5
  import { dirname, join, resolve } from "node:path";
6
6
  import { fileURLToPath } from "node:url";
@@ -13,9 +13,12 @@ import {
13
13
  runProductCli,
14
14
  } from "../host-service/cli/product-cli.mjs";
15
15
 
16
+ import { preflightCompleteUninstall, executeCompleteUninstall } from "../host-service/complete-cli-uninstall.mjs";
17
+
16
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
17
19
  const repoRoot = resolve(__dirname, "..");
18
20
  const setupScript = join(repoRoot, "host-service", "setup.mjs");
21
+ const onboardScript = join(repoRoot, "host-service", "onboard.mjs");
19
22
  const releaseServiceScript = join(
20
23
  repoRoot,
21
24
  "tool_scripts",
@@ -39,11 +42,11 @@ const serviceCommands = new Map([
39
42
  ["service:uninstall", "uninstall"],
40
43
  ]);
41
44
 
42
- function run(command, args) {
45
+ function run(command, args, { setupInput = false } = {}) {
43
46
  const result = spawnSync(command, args, {
44
47
  cwd: repoRoot,
45
48
  env: process.env,
46
- stdio: "inherit",
49
+ stdio: setupInput && process.env.OYSTERUN_SETUP_INPUT_FD !== undefined ? ["inherit", "inherit", "inherit", 3, 4] : "inherit",
47
50
  });
48
51
  if (result.error) {
49
52
  console.error(`oysterun: failed to run ${command}: ${result.error.message}`);
@@ -96,13 +99,16 @@ function parseUninstallArgs(args = []) {
96
99
  options.keepConfig = true;
97
100
  } else if (arg === "--confirm") {
98
101
  const value = args[index + 1];
99
- if (!value) {
102
+ if (!value || value.startsWith("--")) {
100
103
  throw new Error("--confirm requires a value");
101
104
  }
105
+ if (value !== "DELETE") throw new Error("config deletion requires --confirm DELETE exactly");
102
106
  options.confirm = value;
103
107
  index += 1;
104
108
  } else if (arg.startsWith("--confirm=")) {
105
- options.confirm = arg.slice("--confirm=".length);
109
+ const value = arg.slice("--confirm=".length);
110
+ if (value !== "DELETE") throw new Error("config deletion requires --confirm DELETE exactly");
111
+ options.confirm = value;
106
112
  } else if (arg === "--stack") {
107
113
  const value = args[index + 1];
108
114
  if (!value) {
@@ -195,6 +201,7 @@ Usage:
195
201
  oysterun
196
202
  oysterun --version
197
203
  oysterun setup [options]
204
+ oysterun onboard [--package-changed] [--channel latest|beta]
198
205
  oysterun show-qr
199
206
  oysterun uninstall [--confirm DELETE] [--keep-config] [--dry-run]
200
207
  oysterun service:start
@@ -213,6 +220,20 @@ Usage:
213
220
  Product modules:
214
221
  auth, sessions, chat, scheduler, mail, notifications, website, telegram
215
222
 
223
+ Chat transcript reads:
224
+ chat recent|messages|messages-around|search [--session-id <exact_id>]
225
+ A verified live Agent can read its Agent's stopped/history target without
226
+ starting it. History names are unsupported; use an exact retained Session ID.
227
+ chat messages --session-id <history_id> [--limit 20] [--next-batch <opaque>]
228
+ History page limits are 1..100; live messages retain --after-seq.
229
+ History read authority does not authorize send, loop, profile or lifecycle.
230
+
231
+ Session tags:
232
+ sessions start|resume|branch-resume [--tags 'first,second' | --clear-tags]
233
+ sessions profile update [--tags 'first,second' | --clear-tags]
234
+ --clear-tags creates or saves an untagged Session; empty --tags is invalid.
235
+ Omitted start tags inside a Session copy the authenticated parent's name.
236
+
216
237
  First run:
217
238
  If Host config is missing or incomplete, 'oysterun' enters setup.
218
239
  Release setup uses Oysterun Managed Tunnel by default.
@@ -236,8 +257,13 @@ Behavior:
236
257
  - Stops/removes managed Oysterun service state where available.
237
258
  - Preserves the config directory unless --confirm DELETE is supplied exactly.
238
259
  - Does not delete the Default Browse Root or agent folders.
239
- - Does not remove the global npm package; run npm uninstall -g oysterun after
240
- service/config cleanup if you want to remove the CLI itself.
260
+ - Exact --confirm DELETE also removes the same global CLI package that launched
261
+ this command using its matching Node/npm and explicit prefix.
262
+ - Without DELETE (including --keep-config), preserves config and CLI package.
263
+ - --keep-config and --confirm DELETE cannot be combined.
264
+ - --dry-run prints the selected services/config and exact CLI/npm/prefix plan.
265
+ - --stack all removes production and staging services, but only the selected
266
+ config directory and the launching CLI package.
241
267
  `);
242
268
  }
243
269
 
@@ -273,6 +299,31 @@ function runUninstall(args = []) {
273
299
  const configDir = dirname(configPath);
274
300
  const defaultBrowsePath = readConfiguredDefaultBrowsePath();
275
301
  const deleteConfig = options.confirm === "DELETE";
302
+ if (deleteConfig) {
303
+ try {
304
+ const plan = preflightCompleteUninstall({
305
+ cliPath: fileURLToPath(import.meta.url), configDir,
306
+ stack: options.stack || process.env.OYSTERUN_RELEASE_STACK || "production",
307
+ });
308
+ console.log("Oysterun complete uninstall plan:");
309
+ console.log(` Services: ${plan.services.map(t => `${t.name} (${t.label}, port ${t.port || "unconfigured"})`).join(", ")}`);
310
+ console.log(` Config directory: delete ${plan.config}`);
311
+ console.log(` CLI package: remove ${plan.root}`);
312
+ console.log(` npm: ${plan.identity.node_path} ${plan.identity.npm_cli_path} uninstall ${plan.npmArgs.join(" ")} oysterun`);
313
+ console.log(` Prefix: ${plan.prefix}; OS user: ${plan.user.name} (uid ${plan.user.uid})`);
314
+ console.log(" Default Browse Root and agent folders are never deleted by this command.");
315
+ if (options.dryRun) {
316
+ console.log("Dry run complete: no service, config or CLI changes were made.");
317
+ return;
318
+ }
319
+ process.exitCode = executeCompleteUninstall(plan).exitCode;
320
+ } catch (error) {
321
+ console.error(`oysterun uninstall preflight: ${error.message}`);
322
+ console.error("No service, config or CLI changes were made.");
323
+ process.exitCode = 1;
324
+ }
325
+ return;
326
+ }
276
327
  const serviceArgs = ["uninstall"];
277
328
  if (options.stack) {
278
329
  serviceArgs.push("--stack", options.stack);
@@ -290,7 +341,7 @@ function runUninstall(args = []) {
290
341
  defaultBrowsePath ? ` ${defaultBrowsePath}` : ""
291
342
  }`
292
343
  );
293
- console.log(" Global CLI package: preserve");
344
+ console.log(` Global CLI package: preserve ${repoRoot} (npm/prefix removal not requested)`);
294
345
  console.log("");
295
346
 
296
347
  if (!deleteConfig) {
@@ -301,9 +352,7 @@ function runUninstall(args = []) {
301
352
  console.log(
302
353
  "Default Browse Root and agent folders are never deleted by this command; remove them manually only if intended."
303
354
  );
304
- console.log(
305
- "To remove the global CLI after cleanup, run: npm uninstall -g oysterun"
306
- );
355
+ console.log("To remove service, config and this installed CLI together, run: oysterun uninstall --confirm DELETE");
307
356
 
308
357
  if (options.dryRun) {
309
358
  console.log("Dry run complete: no service or config changes were made.");
@@ -325,12 +374,7 @@ function runUninstall(args = []) {
325
374
  process.exit(result.status ?? 1);
326
375
  }
327
376
 
328
- if (deleteConfig) {
329
- rmSync(configDir, { recursive: true, force: true });
330
- console.log(`Deleted config directory: ${configDir}`);
331
- } else {
332
- console.log(`Preserved config directory: ${configDir}`);
333
- }
377
+ console.log(`Preserved config directory: ${configDir}`);
334
378
  }
335
379
 
336
380
  function printConfiguredStatus(status) {
@@ -382,6 +426,9 @@ switch (command) {
382
426
  case "setup":
383
427
  runSetup(rest);
384
428
  break;
429
+ case "onboard":
430
+ run(process.execPath, [onboardScript, ...rest], { setupInput: true });
431
+ break;
385
432
  case "show-qr":
386
433
  run(process.execPath, [setupScript, "--show-qr", ...rest]);
387
434
  break;
@@ -0,0 +1,78 @@
1
+ // @ts-check
2
+
3
+ /**
4
+ * Owns the lifetime of one completed-message navigation request at a time.
5
+ * Timeline movement is deliberately absent from this API: only click() reads.
6
+ *
7
+ * @template TResponse
8
+ * @param {{ readNeighbors: (request: { roomId: string, anchorEventId?: string, anchorPosition?: "latest" }) => Promise<TResponse>, selectTarget: (response: TResponse, direction: "previous" | "next") => string | undefined }} options
9
+ */
10
+ export function createCompletedMessageNeighborClickCoordinator(options) {
11
+ /** @type {string | null} */
12
+ let scope = null;
13
+ let generation = 0;
14
+ /** @type {{ generation: number, scope: string } | null} */
15
+ let active = null;
16
+ let disposed = false;
17
+
18
+ /** @param {string} nextScope */
19
+ function setScope(nextScope) {
20
+ if (scope === nextScope) return;
21
+ scope = nextScope;
22
+ generation += 1;
23
+ active = null;
24
+ }
25
+
26
+ return {
27
+ setScope,
28
+ dispose() {
29
+ disposed = true;
30
+ generation += 1;
31
+ active = null;
32
+ },
33
+ isLoading() {
34
+ return active !== null;
35
+ },
36
+ /**
37
+ * @param {{ scope: string, roomId: string, direction: "previous" | "next", anchorEventId?: string, anchorPosition?: "latest" }} request
38
+ */
39
+ async click(request) {
40
+ if (disposed || request.scope !== scope) return { status: "stale" };
41
+ if (active !== null) return { status: "busy" };
42
+
43
+ const transaction = { generation, scope: request.scope };
44
+ active = transaction;
45
+ try {
46
+ const response = await options.readNeighbors({
47
+ roomId: request.roomId,
48
+ anchorEventId: request.anchorEventId,
49
+ anchorPosition: request.anchorPosition,
50
+ });
51
+ if (
52
+ disposed ||
53
+ active !== transaction ||
54
+ generation !== transaction.generation ||
55
+ scope !== transaction.scope
56
+ ) {
57
+ return { status: "stale" };
58
+ }
59
+ const targetEventId = options.selectTarget(response, request.direction);
60
+ return targetEventId
61
+ ? { status: "target", response, targetEventId }
62
+ : { status: "no_target", response };
63
+ } catch (error) {
64
+ if (
65
+ disposed ||
66
+ active !== transaction ||
67
+ generation !== transaction.generation ||
68
+ scope !== transaction.scope
69
+ ) {
70
+ return { status: "stale" };
71
+ }
72
+ return { status: "error", error };
73
+ } finally {
74
+ if (active === transaction) active = null;
75
+ }
76
+ },
77
+ };
78
+ }
@@ -0,0 +1,205 @@
1
+ // @ts-check
2
+
3
+ export const COMPLETED_MESSAGE_NEIGHBOR_SCHEMA_VERSION =
4
+ "routec.p206_completed_turn_final_message_neighbors.v1";
5
+
6
+ /** @typedef {{ event_id: string, routec_stream_seq: number, origin_server_ts: number | null, provider_turn_id: string }} CompletedMessageNeighborEvent */
7
+ /** @typedef {{ anchor_position: "event", event_id: string, routec_stream_seq: number } | { anchor_position: "latest", event_id: null, routec_stream_seq: number | null }} CompletedMessageNeighborAnchor */
8
+ /** @typedef {{ schema_version: typeof COMPLETED_MESSAGE_NEIGHBOR_SCHEMA_VERSION, lookup_source: string, lookup_strategy: string, order_by: "final_candidate_stream_seq", candidate_predicate: "complete_message_notification_candidate", completion_predicate: "complete_message_notification_completion", navigation_index_version: number | null, navigation_index_names: string[], raw_event_scan_limit: null, raw_event_payload_returned: false, message_body_returned: false }} CompletedMessageNeighborProof */
9
+ /** @typedef {{ status: "ok", session_id: string, matrix_room_id: string, anchor: CompletedMessageNeighborAnchor, previous: CompletedMessageNeighborEvent | null, next: CompletedMessageNeighborEvent | null, boundaries: { no_completed_turns: boolean, at_first_completed_message: boolean, at_latest_completed_message: boolean }, proof: CompletedMessageNeighborProof, committed_transcript_truth?: "matrix_room_timeline", routec_completed_message_neighbor_endpoint?: true, completed_message_truth_source?: "complete_message_notification_candidate_and_completion_predicates", raw_event_payload_returned: false, message_body_returned: false }} CompletedMessageNeighborsResponse */
10
+ /** @typedef {{ sessionId?: string, matrixRoomId?: string, anchorEventId?: string | null, anchorPosition?: "latest" | null }} CompletedMessageNeighborExpectedIdentity */
11
+
12
+ /**
13
+ * @param {unknown} value
14
+ * @returns {value is Record<string, unknown>}
15
+ */
16
+ function isRecord(value) {
17
+ return value !== null && typeof value === "object" && !Array.isArray(value);
18
+ }
19
+
20
+ /**
21
+ * @param {unknown} value
22
+ * @returns {value is string}
23
+ */
24
+ function nonEmptyString(value) {
25
+ return typeof value === "string" && value.trim().length > 0;
26
+ }
27
+
28
+ /**
29
+ * @param {unknown} value
30
+ * @returns {value is number}
31
+ */
32
+ function isNonnegativeSafeInteger(value) {
33
+ return Number.isSafeInteger(value) && Number(value) >= 0;
34
+ }
35
+
36
+ /**
37
+ * @param {unknown} value
38
+ * @returns {value is CompletedMessageNeighborEvent}
39
+ */
40
+ function isNeighbor(value) {
41
+ return (
42
+ isRecord(value) &&
43
+ nonEmptyString(value.event_id) &&
44
+ isNonnegativeSafeInteger(value.routec_stream_seq) &&
45
+ (typeof value.origin_server_ts === "number" ||
46
+ value.origin_server_ts === null) &&
47
+ nonEmptyString(value.provider_turn_id)
48
+ );
49
+ }
50
+
51
+ /**
52
+ * @param {CompletedMessageNeighborsResponse} value
53
+ * @param {CompletedMessageNeighborExpectedIdentity} expected
54
+ */
55
+ function assertRequestIdentity(value, expected) {
56
+ if (expected.sessionId && value.session_id !== expected.sessionId) {
57
+ throw new Error("Completed-message neighbor Session identity mismatch.");
58
+ }
59
+ if (expected.matrixRoomId && value.matrix_room_id !== expected.matrixRoomId) {
60
+ throw new Error("Completed-message neighbor room identity mismatch.");
61
+ }
62
+ if (expected.anchorEventId) {
63
+ if (
64
+ value.anchor.anchor_position !== "event" ||
65
+ value.anchor.event_id !== expected.anchorEventId
66
+ ) {
67
+ throw new Error("Completed-message neighbor anchor identity mismatch.");
68
+ }
69
+ } else if (expected.anchorPosition === "latest") {
70
+ if (
71
+ value.anchor.anchor_position !== "latest" ||
72
+ value.anchor.event_id !== null
73
+ ) {
74
+ throw new Error("Completed-message neighbor latest anchor mismatch.");
75
+ }
76
+ }
77
+ }
78
+
79
+ /**
80
+ * @param {unknown} value
81
+ * @param {CompletedMessageNeighborExpectedIdentity} [expected]
82
+ * @returns {CompletedMessageNeighborsResponse}
83
+ */
84
+ export function assertCompletedMessageNeighborsResponse(value, expected = {}) {
85
+ if (!isRecord(value)) {
86
+ throw new Error("Completed-message neighbor response must be an object.");
87
+ }
88
+ const anchor = value.anchor;
89
+ const boundaries = value.boundaries;
90
+ const proof = value.proof;
91
+ const validAnchor =
92
+ isRecord(anchor) &&
93
+ (anchor.anchor_position === "event" ||
94
+ anchor.anchor_position === "latest") &&
95
+ (typeof anchor.event_id === "string" || anchor.event_id === null) &&
96
+ (isNonnegativeSafeInteger(anchor.routec_stream_seq) ||
97
+ anchor.routec_stream_seq === null) &&
98
+ (anchor.anchor_position === "event"
99
+ ? nonEmptyString(anchor.event_id) &&
100
+ isNonnegativeSafeInteger(anchor.routec_stream_seq)
101
+ : anchor.event_id === null);
102
+ const indexNames = isRecord(proof) ? proof.navigation_index_names : null;
103
+ const indexVersion = isRecord(proof)
104
+ ? proof.navigation_index_version
105
+ : undefined;
106
+ const validIndexDiagnostics =
107
+ Array.isArray(indexNames) &&
108
+ indexNames.every(nonEmptyString) &&
109
+ new Set(indexNames).size === indexNames.length &&
110
+ (Number.isSafeInteger(indexVersion) || indexVersion === null) &&
111
+ ((indexVersion === null && indexNames.length === 0) ||
112
+ (Number.isSafeInteger(indexVersion) && indexNames.length > 0));
113
+ const validProof =
114
+ isRecord(proof) &&
115
+ proof.schema_version === COMPLETED_MESSAGE_NEIGHBOR_SCHEMA_VERSION &&
116
+ nonEmptyString(proof.lookup_source) &&
117
+ nonEmptyString(proof.lookup_strategy) &&
118
+ proof.order_by === "final_candidate_stream_seq" &&
119
+ proof.candidate_predicate === "complete_message_notification_candidate" &&
120
+ proof.completion_predicate === "complete_message_notification_completion" &&
121
+ validIndexDiagnostics &&
122
+ proof.raw_event_scan_limit === null &&
123
+ proof.raw_event_payload_returned === false &&
124
+ proof.message_body_returned === false;
125
+ if (
126
+ value.status !== "ok" ||
127
+ !nonEmptyString(value.session_id) ||
128
+ !nonEmptyString(value.matrix_room_id) ||
129
+ !validAnchor ||
130
+ !(value.previous === null || isNeighbor(value.previous)) ||
131
+ !(value.next === null || isNeighbor(value.next)) ||
132
+ !isRecord(boundaries) ||
133
+ typeof boundaries.no_completed_turns !== "boolean" ||
134
+ typeof boundaries.at_first_completed_message !== "boolean" ||
135
+ typeof boundaries.at_latest_completed_message !== "boolean" ||
136
+ !validProof ||
137
+ !(
138
+ value.committed_transcript_truth === undefined ||
139
+ value.committed_transcript_truth === "matrix_room_timeline"
140
+ ) ||
141
+ !(
142
+ value.routec_completed_message_neighbor_endpoint === undefined ||
143
+ value.routec_completed_message_neighbor_endpoint === true
144
+ ) ||
145
+ !(
146
+ value.completed_message_truth_source === undefined ||
147
+ value.completed_message_truth_source ===
148
+ "complete_message_notification_candidate_and_completion_predicates"
149
+ ) ||
150
+ value.raw_event_payload_returned !== false ||
151
+ value.message_body_returned !== false
152
+ ) {
153
+ throw new Error("Completed-message neighbor response failed validation.");
154
+ }
155
+ const validated = /** @type {CompletedMessageNeighborsResponse} */ (value);
156
+ if (
157
+ validated.boundaries.at_first_completed_message !==
158
+ (validated.previous === null) ||
159
+ validated.boundaries.at_latest_completed_message !==
160
+ (validated.next === null) ||
161
+ (boundaries.no_completed_turns &&
162
+ (validated.previous !== null ||
163
+ validated.next !== null ||
164
+ !boundaries.at_first_completed_message ||
165
+ !boundaries.at_latest_completed_message))
166
+ ) {
167
+ throw new Error("Completed-message neighbor boundaries are inconsistent.");
168
+ }
169
+ if (validated.anchor.anchor_position === "event") {
170
+ if (
171
+ (validated.previous &&
172
+ validated.previous.routec_stream_seq >=
173
+ validated.anchor.routec_stream_seq) ||
174
+ (validated.next &&
175
+ validated.next.routec_stream_seq <= validated.anchor.routec_stream_seq)
176
+ ) {
177
+ throw new Error("Completed-message neighbor ordering is inconsistent.");
178
+ }
179
+ }
180
+ assertRequestIdentity(validated, expected);
181
+ return validated;
182
+ }
183
+
184
+ /**
185
+ * @param {Omit<CompletedMessageNeighborsResponse, "status" | "raw_event_payload_returned" | "message_body_returned">} input
186
+ * @returns {CompletedMessageNeighborsResponse}
187
+ */
188
+ export function buildCompletedMessageNeighborsResponse(input) {
189
+ return assertCompletedMessageNeighborsResponse(
190
+ {
191
+ status: "ok",
192
+ ...input,
193
+ raw_event_payload_returned: false,
194
+ message_body_returned: false,
195
+ },
196
+ {
197
+ sessionId: input.session_id,
198
+ matrixRoomId: input.matrix_room_id,
199
+ anchorEventId:
200
+ input.anchor.anchor_position === "event" ? input.anchor.event_id : null,
201
+ anchorPosition:
202
+ input.anchor.anchor_position === "latest" ? "latest" : null,
203
+ }
204
+ );
205
+ }
@@ -19,12 +19,12 @@ export function evaluateCompletedMessageVisibilityAcknowledgement(input: {
19
19
  currentMatrixRoomId?: string;
20
20
  targetMatrixRoomId?: string;
21
21
  latestCompletedEventId?: string | null;
22
- renderedEventId?: string | null;
22
+ latestTimelineLoaded?: boolean;
23
23
  hasUnreadCompletedMessage?: boolean;
24
24
  documentVisibilityState?: string;
25
25
  documentHasFocus?: boolean;
26
- elementConnected?: boolean;
27
- elementHidden?: boolean;
28
- elementRect?: VisibilityRect | DOMRect;
26
+ visibleMessageConnected?: boolean;
27
+ visibleMessageHidden?: boolean;
28
+ visibleMessageRect?: VisibilityRect | DOMRect;
29
29
  viewportRect?: VisibilityRect | DOMRect;
30
30
  }): { qualified: boolean; reason: string };
@@ -30,13 +30,13 @@ export function evaluateCompletedMessageVisibilityAcknowledgement({
30
30
  currentMatrixRoomId,
31
31
  targetMatrixRoomId,
32
32
  latestCompletedEventId,
33
- renderedEventId,
33
+ latestTimelineLoaded,
34
34
  hasUnreadCompletedMessage,
35
35
  documentVisibilityState,
36
36
  documentHasFocus,
37
- elementConnected,
38
- elementHidden,
39
- elementRect,
37
+ visibleMessageConnected,
38
+ visibleMessageHidden,
39
+ visibleMessageRect,
40
40
  viewportRect,
41
41
  }) {
42
42
  if (routeCChatShell !== true)
@@ -49,19 +49,22 @@ export function evaluateCompletedMessageVisibilityAcknowledgement({
49
49
  }
50
50
  if (hasUnreadCompletedMessage !== true)
51
51
  return { qualified: false, reason: "not_unread" };
52
- if (!latestCompletedEventId || latestCompletedEventId !== renderedEventId) {
53
- return { qualified: false, reason: "stale_or_unrendered_event" };
52
+ if (!latestCompletedEventId || latestTimelineLoaded !== true) {
53
+ return { qualified: false, reason: "latest_timeline_not_ready" };
54
54
  }
55
55
  if (documentVisibilityState !== "visible") {
56
56
  return { qualified: false, reason: "hidden_document" };
57
57
  }
58
58
  if (documentHasFocus !== true)
59
59
  return { qualified: false, reason: "unfocused_document" };
60
- if (elementConnected !== true || elementHidden === true) {
61
- return { qualified: false, reason: "event_not_materially_rendered" };
60
+ if (visibleMessageConnected !== true || visibleMessageHidden === true) {
61
+ return { qualified: false, reason: "no_visible_materialized_message" };
62
62
  }
63
- if (!isCompletedMessageViewportVisible({ elementRect, viewportRect })) {
64
- return { qualified: false, reason: "event_outside_viewport" };
63
+ if (!isCompletedMessageViewportVisible({
64
+ elementRect: visibleMessageRect,
65
+ viewportRect,
66
+ })) {
67
+ return { qualified: false, reason: "no_visible_materialized_message" };
65
68
  }
66
- return { qualified: true, reason: "visible_latest_successful_final" };
69
+ return { qualified: true, reason: "visible_focused_latest_timeline" };
67
70
  }
@@ -0,0 +1,57 @@
1
+ export const DESKTOP_CHAT_SIDEBAR_PREFERENCE_KEY =
2
+ "oysterun.desktop_chat_sidebar_expanded.v1";
3
+
4
+ const readStorage = (windowTarget) => {
5
+ try {
6
+ return windowTarget?.localStorage;
7
+ } catch {
8
+ return undefined;
9
+ }
10
+ };
11
+
12
+ export function readDesktopChatSidebarPreference(windowTarget = globalThis) {
13
+ try {
14
+ const stored = readStorage(windowTarget)?.getItem(
15
+ DESKTOP_CHAT_SIDEBAR_PREFERENCE_KEY
16
+ );
17
+ if (stored === "false") return false;
18
+ if (stored === "true") return true;
19
+ } catch {
20
+ // Browser privacy modes may expose localStorage while rejecting reads.
21
+ }
22
+ return true;
23
+ }
24
+
25
+ export function writeDesktopChatSidebarPreference(
26
+ expanded,
27
+ windowTarget = globalThis
28
+ ) {
29
+ try {
30
+ const storage = readStorage(windowTarget);
31
+ if (typeof storage?.setItem !== "function") return false;
32
+ storage.setItem(
33
+ DESKTOP_CHAT_SIDEBAR_PREFERENCE_KEY,
34
+ expanded === true ? "true" : "false"
35
+ );
36
+ return true;
37
+ } catch {
38
+ // Preference persistence must never make Chat unavailable.
39
+ return false;
40
+ }
41
+ }
42
+
43
+ export function subscribeDesktopChatSidebarPreference(
44
+ listener,
45
+ windowTarget = globalThis
46
+ ) {
47
+ if (typeof listener !== "function") {
48
+ throw new TypeError("Desktop Chat sidebar preference listener is required");
49
+ }
50
+ if (typeof windowTarget?.addEventListener !== "function") return () => {};
51
+ const onStorage = (event) => {
52
+ if (event?.key !== DESKTOP_CHAT_SIDEBAR_PREFERENCE_KEY) return;
53
+ listener(readDesktopChatSidebarPreference(windowTarget));
54
+ };
55
+ windowTarget.addEventListener("storage", onStorage);
56
+ return () => windowTarget.removeEventListener("storage", onStorage);
57
+ }
@@ -2671,6 +2671,16 @@ export function startManagedHostConnection() {
2671
2671
  return getManagedHostBrowserConnection().start();
2672
2672
  }
2673
2673
 
2674
+ // A WebRTC connection is authenticated with the dashboard token used to open
2675
+ // it. Keep the Host's connection-bound authorization policy: a new login must
2676
+ // retire that connection rather than send a new bearer through the old peer.
2677
+ export async function refreshManagedHostAuthentication() {
2678
+ const connection = getManagedHostBrowserConnection();
2679
+ if (connection instanceof ManagedHostBrowserConnection) {
2680
+ await connection.restartWebRtc("dashboard_login", { initiator: "browser" });
2681
+ }
2682
+ }
2683
+
2674
2684
  export function managedHostFetch(input, init) {
2675
2685
  return getManagedHostBrowserConnection().fetch(input, init);
2676
2686
  }