@oar-sh/oar 0.9.0

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 (293) hide show
  1. package/.github/extensions/web-relay/README.md +190 -0
  2. package/.github/extensions/web-relay/extension.mjs +972 -0
  3. package/.github/extensions/web-relay/model-api/model-switching.mjs +338 -0
  4. package/.github/extensions/web-relay/polling/heartbeat.mjs +55 -0
  5. package/.github/extensions/web-relay/polling/polling-loop.mjs +1105 -0
  6. package/.github/extensions/web-relay/runtime/api-client.mjs +34 -0
  7. package/.github/extensions/web-relay/runtime/banner-state.mjs +126 -0
  8. package/.github/extensions/web-relay/runtime/config-loader.mjs +104 -0
  9. package/.github/extensions/web-relay/runtime/debug-log.mjs +27 -0
  10. package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +170 -0
  11. package/.github/extensions/web-relay/runtime/session-binding.mjs +78 -0
  12. package/.github/extensions/web-relay/runtime/session-io.mjs +349 -0
  13. package/.github/extensions/web-relay/runtime/session-join-retry.mjs +44 -0
  14. package/.github/extensions/web-relay/runtime/session-registry.mjs +29 -0
  15. package/.github/extensions/web-relay/runtime/session-runtime-manager.mjs +156 -0
  16. package/.github/extensions/web-relay/runtime/session-sync-bridge.mjs +74 -0
  17. package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +325 -0
  18. package/.github/extensions/web-relay/runtime/workspace-root.mjs +55 -0
  19. package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +386 -0
  20. package/.github/extensions/web-relay/skills/prompt-context.mjs +160 -0
  21. package/.github/extensions/web-relay/skills/question-bridge.mjs +141 -0
  22. package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +463 -0
  23. package/.github/extensions/web-relay/skills/reasoning-stream.mjs +285 -0
  24. package/.github/extensions/web-relay/skills/subagent-lifecycle.mjs +345 -0
  25. package/.github/extensions/web-relay/skills/tool-activity.mjs +243 -0
  26. package/.github/extensions/web-relay/utils/process-utils.mjs +25 -0
  27. package/.github/extensions/web-relay/utils/token-generation.mjs +58 -0
  28. package/CHANGELOG.md +25 -0
  29. package/LICENSE +21 -0
  30. package/README.md +717 -0
  31. package/bin/bat/oar.bat +46 -0
  32. package/bin/oar.js +665 -0
  33. package/package.json +68 -0
  34. package/server/README.md +1379 -0
  35. package/server/claude-worker/claude-attachments.mjs +134 -0
  36. package/server/claude-worker/claude-preview-tool.mjs +57 -0
  37. package/server/claude-worker/claude-sdk-adapter.mjs +420 -0
  38. package/server/claude-worker/claude-session-process.mjs +2391 -0
  39. package/server/claude-worker/claude-session-test-harness.mjs +236 -0
  40. package/server/claude-worker/claude-session-worker.mjs +186 -0
  41. package/server/claude-worker/claude-transcript-relocator.mjs +189 -0
  42. package/server/claude-worker/claude-turn-publisher.mjs +291 -0
  43. package/server/claude-worker/fixtures/workflow-run-record.json +1 -0
  44. package/server/claude-worker/sdk-message-normalizer.mjs +629 -0
  45. package/server/claude-worker/workflow-progress-digest.mjs +269 -0
  46. package/server/copilot-sdk-runtime.mjs +126 -0
  47. package/server/copilot-worker/copilot-attachments.mjs +97 -0
  48. package/server/copilot-worker/copilot-byok-provider.mjs +121 -0
  49. package/server/copilot-worker/copilot-continuation-signals.mjs +349 -0
  50. package/server/copilot-worker/copilot-plan-board.mjs +110 -0
  51. package/server/copilot-worker/copilot-prompt-context.mjs +86 -0
  52. package/server/copilot-worker/copilot-question-bridge.mjs +216 -0
  53. package/server/copilot-worker/copilot-sdk-adapter.mjs +494 -0
  54. package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +800 -0
  55. package/server/copilot-worker/copilot-sdk-session-process.mjs +1729 -0
  56. package/server/copilot-worker/copilot-sdk-session-worker.mjs +167 -0
  57. package/server/copilot-worker/copilot-sdk-test-harness.mjs +329 -0
  58. package/server/copilot-worker/fixtures/abort-turn.json +588 -0
  59. package/server/copilot-worker/fixtures/ask-user-turn.json +1154 -0
  60. package/server/copilot-worker/fixtures/background-timer-continuation.json +445 -0
  61. package/server/copilot-worker/fixtures/background-timer-turn.json +497 -0
  62. package/server/copilot-worker/fixtures/happy-turn.json +566 -0
  63. package/server/copilot-worker/fixtures/quota-turn.json +647 -0
  64. package/server/copilot-worker/fixtures/reasoning-turn.json +625 -0
  65. package/server/copilot-worker/fixtures/resume-turn.json +700 -0
  66. package/server/copilot-worker/fixtures/subagent-turn.json +1378 -0
  67. package/server/copilot-worker/fixtures/tool-permission-turn.json +1293 -0
  68. package/server/cursor-worker/cursor-ask-user-tool.mjs +83 -0
  69. package/server/cursor-worker/cursor-attachments.mjs +88 -0
  70. package/server/cursor-worker/cursor-context-usage.mjs +85 -0
  71. package/server/cursor-worker/cursor-preview-tool.mjs +44 -0
  72. package/server/cursor-worker/cursor-sdk-adapter.mjs +554 -0
  73. package/server/cursor-worker/cursor-session-worker.mjs +145 -0
  74. package/server/cursor-worker/cursor-subagent-roster.mjs +81 -0
  75. package/server/cursor-worker/cursor-turn-runner.mjs +717 -0
  76. package/server/cursor-worker/sdk-message-normalizer.mjs +485 -0
  77. package/server/db-schema.mjs +932 -0
  78. package/server/features.mjs +106 -0
  79. package/server/grok-worker/acp-client.mjs +434 -0
  80. package/server/grok-worker/acp-host-services.mjs +277 -0
  81. package/server/grok-worker/grok-context-usage.mjs +77 -0
  82. package/server/grok-worker/grok-sdk-adapter.mjs +334 -0
  83. package/server/grok-worker/grok-session-worker.mjs +145 -0
  84. package/server/grok-worker/grok-turn-runner.mjs +523 -0
  85. package/server/grok-worker/sdk-message-normalizer.mjs +311 -0
  86. package/server/migrations/0001-add-structured-answer.mjs +80 -0
  87. package/server/migrations/0002-recent-workspace-roots-path-key.mjs +121 -0
  88. package/server/migrations/0003-push-subscriptions.mjs +87 -0
  89. package/server/public/app/action-confirmations.js +267 -0
  90. package/server/public/app/activity-replay-state.mjs +149 -0
  91. package/server/public/app/api-client.js +1082 -0
  92. package/server/public/app/ask-user-view.js +545 -0
  93. package/server/public/app/attachments-view.js +2112 -0
  94. package/server/public/app/auto-compact-window-options.mjs +59 -0
  95. package/server/public/app/background-tasks-view.mjs +494 -0
  96. package/server/public/app/bootstrap.js +4347 -0
  97. package/server/public/app/claude-auth-ui.js +394 -0
  98. package/server/public/app/claude-thinking-options.mjs +38 -0
  99. package/server/public/app/cli-install-ui.js +455 -0
  100. package/server/public/app/code-copy.mjs +85 -0
  101. package/server/public/app/composer-attachment-cache.mjs +151 -0
  102. package/server/public/app/composer-control-state.mjs +107 -0
  103. package/server/public/app/composer-paste.mjs +132 -0
  104. package/server/public/app/context-tier-options.mjs +68 -0
  105. package/server/public/app/context-usage-view.mjs +338 -0
  106. package/server/public/app/conversation-draft-timestamp-utils.mjs +11 -0
  107. package/server/public/app/conversation-preferences.mjs +91 -0
  108. package/server/public/app/conversation-provider-indicator.mjs +88 -0
  109. package/server/public/app/conversation-view.js +2898 -0
  110. package/server/public/app/cwd-menu-placement.mjs +85 -0
  111. package/server/public/app/cwd-picker.js +605 -0
  112. package/server/public/app/emoji-view.js +101 -0
  113. package/server/public/app/external-link-policy.mjs +80 -0
  114. package/server/public/app/file-preview-navigation.mjs +51 -0
  115. package/server/public/app/font-scaling.js +262 -0
  116. package/server/public/app/git-changes-view.js +339 -0
  117. package/server/public/app/git-diff-model.mjs +109 -0
  118. package/server/public/app/grok-auth-ui.js +387 -0
  119. package/server/public/app/image-downscale.mjs +174 -0
  120. package/server/public/app/in-flight-snapshot.mjs +40 -0
  121. package/server/public/app/infinite-loader.js +164 -0
  122. package/server/public/app/journal-view.js +1141 -0
  123. package/server/public/app/known-cwd-options.mjs +41 -0
  124. package/server/public/app/launch-cwd-path.mjs +23 -0
  125. package/server/public/app/live-message-dedupe.mjs +57 -0
  126. package/server/public/app/message-search-view.js +242 -0
  127. package/server/public/app/model-selector-options.mjs +31 -0
  128. package/server/public/app/model-vision-support.mjs +56 -0
  129. package/server/public/app/new-conversation-model-choice.mjs +67 -0
  130. package/server/public/app/plan-usage-view.mjs +307 -0
  131. package/server/public/app/preview-cards.mjs +273 -0
  132. package/server/public/app/preview-command.mjs +103 -0
  133. package/server/public/app/push-settings.js +370 -0
  134. package/server/public/app/pwa-install.js +456 -0
  135. package/server/public/app/question-schema-view.mjs +71 -0
  136. package/server/public/app/reasoning-effort-labels.mjs +42 -0
  137. package/server/public/app/relay-board-view.js +117 -0
  138. package/server/public/app/relay-dot-state.mjs +69 -0
  139. package/server/public/app/relay-error-ctas.mjs +52 -0
  140. package/server/public/app/relay-prompt-sanitizer.mjs +114 -0
  141. package/server/public/app/relay-thoughts.mjs +98 -0
  142. package/server/public/app/relay-watchdog-policy.mjs +29 -0
  143. package/server/public/app/repo-browser-preferences.mjs +79 -0
  144. package/server/public/app/repo-browser-tree-state.mjs +97 -0
  145. package/server/public/app/router.js +657 -0
  146. package/server/public/app/selection-guard.mjs +93 -0
  147. package/server/public/app/settings-modal.js +1484 -0
  148. package/server/public/app/settings-tabs.js +129 -0
  149. package/server/public/app/slash-autocomplete.mjs +188 -0
  150. package/server/public/app/slash-commands.mjs +183 -0
  151. package/server/public/app/socket-handlers.js +703 -0
  152. package/server/public/app/status-store.mjs +242 -0
  153. package/server/public/app/status-view.mjs +315 -0
  154. package/server/public/app/store.js +1454 -0
  155. package/server/public/app/stream-state.mjs +73 -0
  156. package/server/public/app/streaming-dom-patch.mjs +23 -0
  157. package/server/public/app/sync-outbox.mjs +208 -0
  158. package/server/public/app/tap-actions.js +62 -0
  159. package/server/public/app/thread-order.mjs +54 -0
  160. package/server/public/app/tmux-inspector-view.js +422 -0
  161. package/server/public/app/transcript-separators.mjs +282 -0
  162. package/server/public/app-icon-192.png +0 -0
  163. package/server/public/app-icon-512.png +0 -0
  164. package/server/public/app-icon.svg +4 -0
  165. package/server/public/favicon.ico +0 -0
  166. package/server/public/index.html +5743 -0
  167. package/server/public/manifest.webmanifest +32 -0
  168. package/server/public/sw.js +307 -0
  169. package/server/relay-exit-codes.mjs +4 -0
  170. package/server/relay-self-restart.mjs +170 -0
  171. package/server/relay-tools.md +27 -0
  172. package/server/relay.mjs +1026 -0
  173. package/server/repositories/image-conversation-repository.mjs +317 -0
  174. package/server/repositories/message-repository.mjs +434 -0
  175. package/server/repositories/question-repository.mjs +161 -0
  176. package/server/repositories/session-repository.mjs +320 -0
  177. package/server/repositories/share-visibility-statements.mjs +24 -0
  178. package/server/routes/ask-user-routes.mjs +292 -0
  179. package/server/routes/cache-routes.mjs +44 -0
  180. package/server/routes/git-routes.mjs +92 -0
  181. package/server/routes/messages-routes-test-harness.mjs +111 -0
  182. package/server/routes/messages-routes.mjs +7107 -0
  183. package/server/routes/preview-routes.mjs +108 -0
  184. package/server/routes/push-routes.mjs +59 -0
  185. package/server/routes/relay-board-routes.mjs +268 -0
  186. package/server/routes/sessions-routes.mjs +6150 -0
  187. package/server/server-runtime.mjs +7463 -0
  188. package/server/server.js +24 -0
  189. package/server/services/ask-user-routing-service.mjs +117 -0
  190. package/server/services/cache-rebuild-service.mjs +236 -0
  191. package/server/services/claude-auth-service.mjs +578 -0
  192. package/server/services/claude-context-usage.mjs +240 -0
  193. package/server/services/claude-session-root-service.mjs +228 -0
  194. package/server/services/cli-install-service.mjs +1152 -0
  195. package/server/services/cli-process-runner.mjs +181 -0
  196. package/server/services/cloudflared-tunnel-service.mjs +358 -0
  197. package/server/services/context-snapshot-service.mjs +499 -0
  198. package/server/services/context-usage-view.mjs +160 -0
  199. package/server/services/conversation-preferences-service.mjs +208 -0
  200. package/server/services/cursor-dashboard-usage.mjs +217 -0
  201. package/server/services/cursor-session-root-service.mjs +62 -0
  202. package/server/services/delete-archive-service.mjs +154 -0
  203. package/server/services/drives-path-helpers.mjs +66 -0
  204. package/server/services/fixtures/claude-auth-stub.sh +62 -0
  205. package/server/services/fixtures/cli-install-stub.sh +75 -0
  206. package/server/services/fixtures/grok-stub.sh +86 -0
  207. package/server/services/generated-image-cleanup-service.mjs +114 -0
  208. package/server/services/git-changes-service.mjs +195 -0
  209. package/server/services/github-billing-usage.mjs +133 -0
  210. package/server/services/grok-auth-service.mjs +593 -0
  211. package/server/services/grok-billing-usage.mjs +120 -0
  212. package/server/services/image-context-service.mjs +106 -0
  213. package/server/services/image-operation-service.mjs +135 -0
  214. package/server/services/keyed-mutex-service.mjs +82 -0
  215. package/server/services/mime-sniffer.mjs +118 -0
  216. package/server/services/oar-cli-helpers.mjs +109 -0
  217. package/server/services/oar-state-migration-service.mjs +243 -0
  218. package/server/services/plan-usage-claude.mjs +387 -0
  219. package/server/services/plan-usage-contract.mjs +271 -0
  220. package/server/services/plan-usage-copilot.mjs +421 -0
  221. package/server/services/plan-usage-cursor.mjs +566 -0
  222. package/server/services/plan-usage-grok.mjs +436 -0
  223. package/server/services/plan-usage-service.mjs +487 -0
  224. package/server/services/preview-health-probe.mjs +82 -0
  225. package/server/services/preview-proxy-server.mjs +498 -0
  226. package/server/services/preview-registry-service.mjs +457 -0
  227. package/server/services/preview-static-handler.mjs +237 -0
  228. package/server/services/provider-model-selection.mjs +91 -0
  229. package/server/services/provider-reasoning-effort.mjs +94 -0
  230. package/server/services/push-active-device-service.mjs +57 -0
  231. package/server/services/push-dispatch-service.mjs +459 -0
  232. package/server/services/rate-limit-service.mjs +63 -0
  233. package/server/services/relay-bridge-owner-service.mjs +84 -0
  234. package/server/services/relay-cli-launcher-service.mjs +209 -0
  235. package/server/services/relay-prompt-sanitizer.mjs +137 -0
  236. package/server/services/relay-queue-gate-service.mjs +38 -0
  237. package/server/services/relay-restart-orchestrator-service.mjs +935 -0
  238. package/server/services/relay-singleton-guard.mjs +124 -0
  239. package/server/services/safe-served-content.mjs +57 -0
  240. package/server/services/sdk-session-import-service.mjs +271 -0
  241. package/server/services/sdk-session-sync-service.mjs +207 -0
  242. package/server/services/session-history-refresh-service.mjs +497 -0
  243. package/server/services/session-transcript-service.mjs +419 -0
  244. package/server/services/session-worker-launch-service.mjs +786 -0
  245. package/server/services/session-worker-process-service.mjs +319 -0
  246. package/server/services/session-worker-registry-service.mjs +194 -0
  247. package/server/services/session-worker-stop-service.mjs +141 -0
  248. package/server/services/session-worker-supervisor-service.mjs +898 -0
  249. package/server/services/session-worker-websocket-service.mjs +483 -0
  250. package/server/services/ssh-tunnel-manager-service.mjs +423 -0
  251. package/server/services/status-event-service.mjs +158 -0
  252. package/server/services/tmux-inspector-access-policy.mjs +77 -0
  253. package/server/services/tmux-inspector-socket-service.mjs +191 -0
  254. package/server/services/tmux-inspector-stream-service.mjs +552 -0
  255. package/server/services/tunnel-worker-path-guard.mjs +107 -0
  256. package/server/services/upload-sweep.mjs +60 -0
  257. package/server/services/usage-snapshot-helpers.mjs +209 -0
  258. package/server/services/windows-autostart-service.mjs +115 -0
  259. package/server/services/workspace-root-defaults-service.mjs +34 -0
  260. package/server/services/workspace-root-path-policy.mjs +196 -0
  261. package/server/services/workspace-root-relaunch-service.mjs +132 -0
  262. package/server/services/workspace-symlink-guard.mjs +54 -0
  263. package/server/tools/capture-proxy.mjs +173 -0
  264. package/server/tty-console-bootstrap.mjs +75 -0
  265. package/server/uploads/README.md +36 -0
  266. package/server/uploads/image_input.md +540 -0
  267. package/server/windows-terminal-launcher.mjs +29 -0
  268. package/server/workspace-root.mjs +87 -0
  269. package/shared/ask-user-bridge.mjs +131 -0
  270. package/shared/auto-compact-window.mjs +116 -0
  271. package/shared/background-task-timeout.mjs +52 -0
  272. package/shared/claude-thinking.mjs +101 -0
  273. package/shared/context-window-fallbacks.mjs +148 -0
  274. package/shared/control-poller.mjs +115 -0
  275. package/shared/empty-turn-completion.mjs +20 -0
  276. package/shared/model-catalog-freshness.mjs +11 -0
  277. package/shared/model-descriptors.mjs +118 -0
  278. package/shared/model-id.mjs +147 -0
  279. package/shared/model-refresh.mjs +24 -0
  280. package/shared/openai-reasoning.mjs +13 -0
  281. package/shared/plan-lines.mjs +13 -0
  282. package/shared/preview-instructions.mjs +77 -0
  283. package/shared/preview-tool-core.mjs +191 -0
  284. package/shared/provider-routing.mjs +29 -0
  285. package/shared/question-schema.mjs +315 -0
  286. package/shared/question-timeout.mjs +18 -0
  287. package/shared/stream-emit-gating.mjs +28 -0
  288. package/shared/subagent-run-id.mjs +17 -0
  289. package/shared/thought-cap.mjs +13 -0
  290. package/shared/tool-activity.mjs +98 -0
  291. package/shared/turn-ceiling.mjs +53 -0
  292. package/shared/worker-bootstrap.mjs +44 -0
  293. package/shared/worker-crash-guard.mjs +60 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Resolves the relay status dot's colour and tooltip.
3
+ *
4
+ * The dot's primary meaning is unchanged: grey when the web relay is
5
+ * unreachable, coloured when it answers. Cloudflare Tunnel mode adds a third
6
+ * colour so it is obvious at a glance that the relay is reachable from the
7
+ * internet rather than only from this machine.
8
+ *
9
+ * A managed-but-disconnected tunnel stays green rather than going grey: the
10
+ * relay itself is still reachable, and grey would claim otherwise. The tooltip
11
+ * carries the bad news instead. Note this state is only ever visible locally —
12
+ * a remote browser whose tunnel is down cannot load the page at all.
13
+ *
14
+ * `tone` is the same state as `className`, minus the dot's CSS baggage. The
15
+ * mobile burger toggle dyes its bars from it, because the dot itself sits in
16
+ * the off-canvas sidebar and is invisible whenever the conversation list is
17
+ * closed. Both indicators read the one tone so they cannot disagree.
18
+ */
19
+
20
+ function normalizeTunnel(tunnel) {
21
+ if (!tunnel || typeof tunnel !== 'object') return null;
22
+ const mode = String(tunnel.mode || '').trim().toLowerCase();
23
+ const managed = mode === 'managed' || tunnel.enabled === true;
24
+ if (!managed) return null;
25
+ return {
26
+ connected: tunnel.connected === true,
27
+ lastError: String(tunnel.lastError || '').trim(),
28
+ };
29
+ }
30
+
31
+ function describeWorkers({ processingCount = 0, errorCount = 0, questionCount = 0 } = {}) {
32
+ const plural = (count) => (count === 1 ? '' : 's');
33
+ if (processingCount > 0) return `${processingCount} session worker${plural(processingCount)} processing`;
34
+ if (errorCount > 0) return `${errorCount} session worker${plural(errorCount)} degraded`;
35
+ if (questionCount > 0) return `${questionCount} session worker${plural(questionCount)} waiting on a question`;
36
+ return '';
37
+ }
38
+
39
+ export function resolveRelayDotState({
40
+ relayOnline = false,
41
+ cliOnline = false,
42
+ cloudflaredTunnel = null,
43
+ processingCount = 0,
44
+ errorCount = 0,
45
+ questionCount = 0,
46
+ } = {}) {
47
+ if (!relayOnline) {
48
+ return { className: 'offline', tone: 'offline', title: 'Web relay unreachable' };
49
+ }
50
+
51
+ const tunnel = normalizeTunnel(cloudflaredTunnel);
52
+ const tunnelled = !!tunnel && tunnel.connected;
53
+ const tone = tunnelled ? 'tunnelled' : 'online';
54
+ const className = tunnelled ? 'online tunnelled' : 'online';
55
+
56
+ let reach = 'Web relay reachable';
57
+ if (tunnelled) {
58
+ reach = 'Web relay reachable via Cloudflare Tunnel';
59
+ } else if (tunnel) {
60
+ reach = tunnel.lastError
61
+ ? `Web relay reachable; Cloudflare tunnel disconnected (${tunnel.lastError})`
62
+ : 'Web relay reachable; Cloudflare tunnel disconnected';
63
+ }
64
+
65
+ if (!cliOnline) return { className, tone, title: `${reach}; CLI offline` };
66
+
67
+ const workers = describeWorkers({ processingCount, errorCount, questionCount });
68
+ return { className, tone, title: workers ? `${reach}; ${workers}` : reach };
69
+ }
@@ -0,0 +1,52 @@
1
+ // Turns a terminal turn failure in the transcript into a button that fixes it.
2
+ //
3
+ // `buildTerminalFailureTextForChat()` (messages-routes.mjs) renders every
4
+ // terminal failure as plain chat text ending in `Error code: relay.<code>.`,
5
+ // so the code is the only stable handle a client has. The dead end this exists
6
+ // for: a Grok turn fails with relay.grok-cli-missing and the printed advice is
7
+ // "install it on the relay host" — i.e. open a shell, the exact thing the relay
8
+ // exists to avoid.
9
+ //
10
+ // Pure and DOM-free on purpose: the table is the contract, the caller owns the
11
+ // markup and the escaping.
12
+
13
+ // Codes are matched post-normalisation: normalizeTerminalErrorCode() lowercases
14
+ // and rewrites every non-alphanumeric run to a dash, so `grok.cli_missing`
15
+ // reaches the transcript as `relay.grok-cli-missing`.
16
+ const RELAY_ERROR_CTAS = Object.freeze({
17
+ 'grok-cli-missing': Object.freeze([
18
+ Object.freeze({ action: 'install-grok-cli', label: 'Install Grok CLI' }),
19
+ Object.freeze({ action: 'open-grok-settings', label: 'Grok settings' }),
20
+ ]),
21
+ 'grok-authentication-failed': Object.freeze([
22
+ Object.freeze({ action: 'sign-in-to-grok', label: 'Sign in to Grok' }),
23
+ Object.freeze({ action: 'open-grok-settings', label: 'Grok settings' }),
24
+ ]),
25
+ // Shipped with the Claude relogin plan (§4.3 there) as the deep link its
26
+ // reworded message points at.
27
+ 'claude-authentication-failed': Object.freeze([
28
+ Object.freeze({ action: 'open-claude-settings', label: 'Claude settings' }),
29
+ ]),
30
+ });
31
+
32
+ const STABLE_CODE_PATTERN = /error code:\s*relay\.([a-z0-9-]+)/i;
33
+
34
+ export function relayErrorCodeFromText(text) {
35
+ const match = String(text || '').match(STABLE_CODE_PATTERN);
36
+ return match ? match[1].toLowerCase() : '';
37
+ }
38
+
39
+ /**
40
+ * The actions a terminal failure offers, or an empty array. Never throws and
41
+ * never guesses: an unknown code simply has no CTA, which is the current
42
+ * behaviour for every failure that is not in the table.
43
+ */
44
+ export function relayErrorCtaActions(text) {
45
+ const code = relayErrorCodeFromText(text);
46
+ return code && RELAY_ERROR_CTAS[code] ? RELAY_ERROR_CTAS[code] : [];
47
+ }
48
+
49
+ export function relayErrorCtaActionsForCode(code) {
50
+ const normalized = String(code || '').trim().toLowerCase().replace(/^relay\./, '');
51
+ return RELAY_ERROR_CTAS[normalized] || [];
52
+ }
@@ -0,0 +1,114 @@
1
+ const RELAY_TOOL_GUIDANCE = [
2
+ '# Relay Tool Guidance',
3
+ 'For any user-facing question or clarification, use the ask_user tool so the web relay can render question cards and buttons. Never ask questions in plain assistant text.',
4
+ 'When using ask_user, ALWAYS include a `choices` array with 2-6 answer options so the web relay can render clickable buttons. Only omit choices when the question genuinely requires freeform text input.',
5
+ 'In autopilot, still call ask_user when user input is truly blocking, because the relay bridge can surface the question even when the direct SDK question hook is bypassed.',
6
+ 'For relay restarts in extension-managed mode, require explicit user permission first, then use the authenticated localhost API `POST /api/relay/shutdown`. Do not restart by killing processes or using respawn scripts.',
7
+ 'Note: shutdown is queued and only completes when the current turn finishes, so it is pointless to wait for it to interrupt an active turn.',
8
+ 'Use `restart: true` in the request body when the user wants a real relay restart rather than a plain shutdown. Example request body: `{ "reason": "manual-restart", "requestedBy": "localhost-api", "restart": true }`.',
9
+ ].join(' ');
10
+
11
+ const MODE_MARKERS = {
12
+ plan: '[Relay mode: plan]',
13
+ ask: '[Relay mode: ask]',
14
+ autopilot: '[Relay mode: autopilot]',
15
+ agent: '[Relay mode: agent]',
16
+ };
17
+
18
+ const MODE_INSTRUCTIONS = {
19
+ plan: [
20
+ 'Draft a concise plan only.',
21
+ 'Use read-only inspection tools (glob, rg, view) only when they materially improve plan quality; otherwise draft from provided context.',
22
+ 'Do not edit repository files or run mutating commands unless the user explicitly asks for implementation.',
23
+ 'If clarification is required, pause and ask through the web relay.',
24
+ 'These instructions remain in effect until relay mode changes.',
25
+ ].join(' '),
26
+ ask: [
27
+ 'Prioritize clarification questions before doing any implementation work.',
28
+ 'If the request is ambiguous or underspecified, pause and ask through the web relay before making assumptions.',
29
+ 'Do not make broad assumptions when a question would materially change the result.',
30
+ 'These instructions remain in effect until relay mode changes.',
31
+ ].join(' '),
32
+ autopilot: [
33
+ 'Act directly on the request and use tools when needed.',
34
+ 'Keep moving unless user input is truly blocking.',
35
+ 'These instructions remain in effect until relay mode changes.',
36
+ ].join(' '),
37
+ agent: [
38
+ 'Proceed as an interactive coding agent and use tools as needed.',
39
+ 'If you need clarification, pause and ask through the web relay instead of stalling silently.',
40
+ 'These instructions remain in effect until relay mode changes.',
41
+ ].join(' '),
42
+ };
43
+
44
+ const LEGACY_MODE_INSTRUCTIONS = {
45
+ plan: [
46
+ 'Draft a concise plan only.',
47
+ 'Do not edit files or run tools unless the user explicitly asks for implementation.',
48
+ 'If clarification is required, pause and ask through the web relay.',
49
+ ].join(' '),
50
+ ask: [
51
+ 'Prioritize clarification questions before doing any implementation work.',
52
+ 'If the request is ambiguous or underspecified, pause and ask through the web relay before making assumptions.',
53
+ 'Do not make broad assumptions when a question would materially change the result.',
54
+ ].join(' '),
55
+ autopilot: [
56
+ 'Act directly on the request and use tools when needed.',
57
+ 'Keep moving unless user input is truly blocking.',
58
+ ].join(' '),
59
+ agent: [
60
+ 'Proceed as an interactive coding agent and use tools as needed.',
61
+ 'If you need clarification, pause and ask through the web relay instead of stalling silently.',
62
+ ].join(' '),
63
+ };
64
+
65
+ function escapeRegExp(value) {
66
+ return String(value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
67
+ }
68
+
69
+ function whitespaceFlexiblePattern(value) {
70
+ return String(value || '')
71
+ .trim()
72
+ .split(/\s+/)
73
+ .filter(Boolean)
74
+ .map((part) => escapeRegExp(part))
75
+ .join('\\s+');
76
+ }
77
+
78
+ function buildPromptPrefixPatterns(mode) {
79
+ const requestedMode = String(mode || '').trim().toLowerCase();
80
+ const modes = requestedMode && MODE_MARKERS[requestedMode]
81
+ ? [requestedMode]
82
+ : Object.keys(MODE_MARKERS);
83
+ return modes.flatMap((key) => {
84
+ const marker = MODE_MARKERS[key];
85
+ const currentFullPrefix = `${marker} ${MODE_INSTRUCTIONS[key]} ${RELAY_TOOL_GUIDANCE}`;
86
+ const legacyFullPrefix = `${marker} ${LEGACY_MODE_INSTRUCTIONS[key]} ${RELAY_TOOL_GUIDANCE}`;
87
+ return [
88
+ new RegExp(`^${whitespaceFlexiblePattern(currentFullPrefix)}\\s*`, 'i'),
89
+ new RegExp(`^${whitespaceFlexiblePattern(legacyFullPrefix)}\\s*`, 'i'),
90
+ new RegExp(`^${whitespaceFlexiblePattern(marker)}\\s*`, 'i'),
91
+ ];
92
+ });
93
+ }
94
+
95
+ function stripAttachmentPromptArtifacts(text) {
96
+ return String(text || '')
97
+ .replace(/<current_datetime>[\s\S]*?<\/current_datetime>/gi, '')
98
+ .replace(/<system_reminder>[\s\S]*?<\/system_reminder>/gi, '')
99
+ .replace(/\[Attached file references\][\s\S]*?\[\/Attached file references\]/gi, '')
100
+ .replace(/\[Attached image(?:s)?\s*:[\s\S]*?\]/gi, '')
101
+ .replace(/\[Attached file\s*:[\s\S]*?\]/gi, '')
102
+ .trim();
103
+ }
104
+
105
+ export function stripRelayPromptContext(text, relayMode = '') {
106
+ const value = stripAttachmentPromptArtifacts(text);
107
+ if (!value) return '';
108
+ const patterns = buildPromptPrefixPatterns(relayMode);
109
+ for (const pattern of patterns) {
110
+ const stripped = value.replace(pattern, '').trim();
111
+ if (stripped && stripped !== value) return stripped;
112
+ }
113
+ return value;
114
+ }
@@ -0,0 +1,98 @@
1
+ function normalizeSeq(value) {
2
+ const num = Number(value);
3
+ if (!Number.isFinite(num)) return null;
4
+ return Math.max(0, Math.trunc(num));
5
+ }
6
+
7
+ function normalizeTimestampMs(value) {
8
+ const text = String(value || '').trim();
9
+ if (!text) return null;
10
+ const parsed = Date.parse(text);
11
+ return Number.isFinite(parsed) ? parsed : null;
12
+ }
13
+
14
+ function toThoughtEntry(value, order) {
15
+ const text = String(value?.text || '');
16
+ if (!text.trim()) return null;
17
+ const reasoningId = String(value?.reasoningId || '').trim() || null;
18
+ const timestamp = String(value?.timestamp || '').trim() || null;
19
+ const subagentRunId = String(value?.subagentRunId || '').trim() || null;
20
+ return {
21
+ reasoningId,
22
+ seq: normalizeSeq(value?.seq),
23
+ text,
24
+ done: !!value?.done,
25
+ timestamp,
26
+ subagentRunId,
27
+ _order: order,
28
+ _timestampMs: normalizeTimestampMs(timestamp),
29
+ };
30
+ }
31
+
32
+ function isCandidateNewer(next, prev) {
33
+ if (!prev) return true;
34
+ if (!next) return false;
35
+ if (Number.isFinite(next.seq) && Number.isFinite(prev.seq) && next.seq !== prev.seq) {
36
+ return next.seq > prev.seq;
37
+ }
38
+ if (Number.isFinite(next._timestampMs) && Number.isFinite(prev._timestampMs) && next._timestampMs !== prev._timestampMs) {
39
+ return next._timestampMs > prev._timestampMs;
40
+ }
41
+ if (next.text.length !== prev.text.length) {
42
+ return next.text.length > prev.text.length;
43
+ }
44
+ return next._order > prev._order;
45
+ }
46
+
47
+ function sortThoughtEntries(a, b) {
48
+ if (Number.isFinite(a.seq) && Number.isFinite(b.seq) && a.seq !== b.seq) {
49
+ return a.seq - b.seq;
50
+ }
51
+ if (Number.isFinite(a._timestampMs) && Number.isFinite(b._timestampMs) && a._timestampMs !== b._timestampMs) {
52
+ return a._timestampMs - b._timestampMs;
53
+ }
54
+ return a._order - b._order;
55
+ }
56
+
57
+ function toArray(value) {
58
+ if (!value) return [];
59
+ if (Array.isArray(value)) return value;
60
+ if (value instanceof Map) return Array.from(value.values());
61
+ return [];
62
+ }
63
+
64
+ export function normalizeRelayThoughtList(thoughts = []) {
65
+ const normalized = toArray(thoughts)
66
+ .map((entry, index) => toThoughtEntry(entry, index))
67
+ .filter(Boolean);
68
+
69
+ const byReasoningId = new Map();
70
+ const passthrough = [];
71
+ for (const entry of normalized) {
72
+ if (!entry.reasoningId) {
73
+ passthrough.push(entry);
74
+ continue;
75
+ }
76
+ const existing = byReasoningId.get(entry.reasoningId);
77
+ if (!existing || isCandidateNewer(entry, existing)) {
78
+ byReasoningId.set(entry.reasoningId, entry);
79
+ }
80
+ }
81
+
82
+ const ordered = [...byReasoningId.values(), ...passthrough].sort(sortThoughtEntries);
83
+ return ordered.map((entry) => ({
84
+ reasoningId: entry.reasoningId,
85
+ seq: Number.isFinite(entry.seq) ? entry.seq : null,
86
+ text: entry.text,
87
+ done: !!entry.done,
88
+ timestamp: entry.timestamp || null,
89
+ subagentRunId: entry.subagentRunId || null,
90
+ }));
91
+ }
92
+
93
+ export function mergeRelayThoughts(persistedThoughts = [], cachedThoughts = []) {
94
+ return normalizeRelayThoughtList([
95
+ ...toArray(persistedThoughts),
96
+ ...toArray(cachedThoughts),
97
+ ]);
98
+ }
@@ -0,0 +1,29 @@
1
+ // Escalation policy for the relay connection watchdog. ensureSocketConnected()
2
+ // only ever calls socket.connect(), which socket.io silently ignores while the
3
+ // manager believes a reconnect cycle is in flight (`_reconnecting`) or the
4
+ // socket still claims to be connected. Both states can outlive an Android
5
+ // freeze whose timers were dropped, leaving connect() a permanent no-op. The
6
+ // watchdog therefore counts consecutive ticks spent disconnected and, past a
7
+ // threshold, escalates to a hard reset that rebuilds the manager state machine.
8
+
9
+ // Four 5s ticks ≈ 20s. A healthy manager mid-backoff completes a full retry
10
+ // cycle inside that window (10s max delay + 10s connect timeout only overlaps
11
+ // when the network is genuinely down, where a reset is harmless churn), so a
12
+ // hard reset this late only fires on wedged or dead-network states.
13
+ export const RELAY_WATCHDOG_HARD_RESET_TICKS = 4;
14
+
15
+ /**
16
+ * Decide what this watchdog tick should do.
17
+ * @param {{ state: 'connected'|'retrying'|'forced'|'disabled', disconnectedTicks: number, hardResetTicks?: number }} input
18
+ * @returns {{ disconnectedTicks: number, hardReset: boolean }}
19
+ */
20
+ export function adviseWatchdogTick({ state, disconnectedTicks, hardResetTicks = RELAY_WATCHDOG_HARD_RESET_TICKS }) {
21
+ if (state === 'connected' || state === 'disabled') {
22
+ return { disconnectedTicks: 0, hardReset: false };
23
+ }
24
+ const next = (Number(disconnectedTicks) || 0) + 1;
25
+ if (next >= hardResetTicks) {
26
+ return { disconnectedTicks: 0, hardReset: true };
27
+ }
28
+ return { disconnectedTicks: next, hardReset: false };
29
+ }
@@ -0,0 +1,79 @@
1
+ // Browser-persisted repo browser toolbar filters.
2
+ //
3
+ // Kept in its own module (rather than inline in store.js) so it stays free of
4
+ // `window`/`document` at module scope and can be unit-tested under plain node.
5
+ // Storage is reached only inside the functions, via an injectable accessor.
6
+
7
+ export const REPO_BROWSER_WORKSPACE_HIDDEN_STORAGE_KEY = 'copilot_repo_browser_hidden_workspace';
8
+ export const REPO_BROWSER_DRIVES_HIDDEN_STORAGE_KEY = 'copilot_repo_browser_hidden_drives';
9
+ export const REPO_BROWSER_WORKSPACE_HEAVY_STORAGE_KEY = 'copilot_repo_browser_heavy_workspace';
10
+
11
+ const TRUE_VALUES = new Set(['1', 'true', 'on', 'yes']);
12
+ const FALSE_VALUES = new Set(['0', 'false', 'off', 'no', '']);
13
+
14
+ function defaultStorage() {
15
+ try {
16
+ return globalThis.localStorage || null;
17
+ } catch {
18
+ // Storage access throws outright in some privacy modes.
19
+ return null;
20
+ }
21
+ }
22
+
23
+ export function normalizeStoredBooleanFlag(rawValue, fallback = false) {
24
+ if (rawValue === null || rawValue === undefined) return fallback;
25
+ const text = String(rawValue).trim().toLowerCase();
26
+ if (TRUE_VALUES.has(text)) return true;
27
+ if (FALSE_VALUES.has(text)) return false;
28
+ return fallback;
29
+ }
30
+
31
+ export function readStoredBooleanFlag(key, { fallback = false, storage = defaultStorage() } = {}) {
32
+ if (!storage) return fallback;
33
+ try {
34
+ return normalizeStoredBooleanFlag(storage.getItem(key), fallback);
35
+ } catch {
36
+ return fallback;
37
+ }
38
+ }
39
+
40
+ export function writeStoredBooleanFlag(key, value, { storage = defaultStorage() } = {}) {
41
+ if (!storage) return false;
42
+ try {
43
+ storage.setItem(key, value ? '1' : '0');
44
+ return true;
45
+ } catch {
46
+ return false;
47
+ }
48
+ }
49
+
50
+ /**
51
+ * Every filter defaults to off, so a fresh browser profile behaves exactly as
52
+ * the pre-persistence build did.
53
+ */
54
+ export function readRepoBrowserPreferences(storage = defaultStorage()) {
55
+ return {
56
+ workspaceIncludeHidden: readStoredBooleanFlag(REPO_BROWSER_WORKSPACE_HIDDEN_STORAGE_KEY, { storage }),
57
+ drivesIncludeHidden: readStoredBooleanFlag(REPO_BROWSER_DRIVES_HIDDEN_STORAGE_KEY, { storage }),
58
+ workspaceIncludeHeavy: readStoredBooleanFlag(REPO_BROWSER_WORKSPACE_HEAVY_STORAGE_KEY, { storage }),
59
+ };
60
+ }
61
+
62
+ /**
63
+ * The workspace root and the drives/session roots keep independent hidden-file
64
+ * flags in state (the toolbar even labels them differently: "Hidden" vs
65
+ * "Hidden/System"), so they persist under separate keys.
66
+ */
67
+ export function repoBrowserHiddenStorageKeyForRoot(activeRoot) {
68
+ return String(activeRoot || '').trim().toLowerCase() === 'workspace'
69
+ ? REPO_BROWSER_WORKSPACE_HIDDEN_STORAGE_KEY
70
+ : REPO_BROWSER_DRIVES_HIDDEN_STORAGE_KEY;
71
+ }
72
+
73
+ export function writeRepoBrowserHiddenPreference(activeRoot, value, storage = defaultStorage()) {
74
+ return writeStoredBooleanFlag(repoBrowserHiddenStorageKeyForRoot(activeRoot), value, { storage });
75
+ }
76
+
77
+ export function writeRepoBrowserHeavyPreference(value, storage = defaultStorage()) {
78
+ return writeStoredBooleanFlag(REPO_BROWSER_WORKSPACE_HEAVY_STORAGE_KEY, value, { storage });
79
+ }
@@ -0,0 +1,97 @@
1
+ // Pure helpers for keeping the repo browser's tree expansion alive across a
2
+ // refetch (hidden/heavy toggle, Refresh button).
3
+ //
4
+ // The tree the server returns is lazy: `/api/repo/tree` ships the root plus its
5
+ // direct children only, every dir child marked `lazy: true, childrenLoaded:
6
+ // false`. So restoring expansion is not just a matter of keeping the
7
+ // `expandedPaths` Set — each still-open branch has to be re-fetched, parent
8
+ // first, before it can render as anything other than "Expand to load entries…".
9
+
10
+ export const REPO_REHYDRATE_MAX_PATHS = 24;
11
+
12
+ /**
13
+ * Root-first ancestor chain for a repo browser path, always including the
14
+ * synthetic root `''` and the path itself.
15
+ *
16
+ * '' -> ['']
17
+ * 'a/b/c' -> ['', 'a', 'a/b', 'a/b/c']
18
+ * '/home/s/x' -> ['', '/', '/home', '/home/s', '/home/s/x']
19
+ * 'C:/Users/x' -> ['', 'C:', 'C:/Users', 'C:/Users/x']
20
+ *
21
+ * Windows drive paths fall out of the relative branch unchanged — they are not
22
+ * special-cased, matching the walks this replaces.
23
+ */
24
+ export function repoAncestorPaths(pathValue) {
25
+ const value = String(pathValue ?? '').trim();
26
+ if (!value) return [''];
27
+ const absolute = value.startsWith('/');
28
+ const chain = absolute ? ['', '/'] : [''];
29
+ if (value === '/') return chain;
30
+ const parts = value.split('/').filter(Boolean);
31
+ let rolling = '';
32
+ for (const part of parts) {
33
+ rolling = rolling ? `${rolling}/${part}` : (absolute ? `/${part}` : part);
34
+ chain.push(rolling);
35
+ }
36
+ return chain;
37
+ }
38
+
39
+ /**
40
+ * Deepest ancestor of `pathValue` (inclusive) that still resolves to a
41
+ * directory, falling back to `''` when the whole branch is gone. This is what
42
+ * makes turning hidden files back off safe: the dotfile directory the user was
43
+ * sitting in simply vanishes from the refetched tree.
44
+ */
45
+ export function deepestExistingAncestor(pathValue, hasDirPath) {
46
+ const chain = repoAncestorPaths(pathValue);
47
+ for (let index = chain.length - 1; index >= 0; index -= 1) {
48
+ const candidate = chain[index];
49
+ if (candidate === '') return '';
50
+ if (typeof hasDirPath === 'function' && hasDirPath(candidate)) return candidate;
51
+ }
52
+ return '';
53
+ }
54
+
55
+ /**
56
+ * Ordered list of directories to re-fetch after a refresh, parents always
57
+ * before children (the caller walks it sequentially — `ensureRepoChildrenLoaded`
58
+ * rebuilds `nodeMap` on each success, and a child is only reachable once its
59
+ * parent's children have landed).
60
+ *
61
+ * Tier 1 — the current path's ancestor chain — is never capped: it is the
62
+ * correctness fix and is bounded by directory depth anyway. Tier 2 — other
63
+ * previously-expanded branches — is capped, and an expanded path whose parent
64
+ * did not make the plan is dropped along with its descendants. That is exact
65
+ * rather than heuristic: `renderRepoTreeNode` only descends into loaded
66
+ * `children`, so such a node renders the lazy placeholder no matter what
67
+ * `expandedPaths` says. Overflow degrades to today's behaviour (placeholder,
68
+ * one click to load), never to breakage.
69
+ */
70
+ export function planRepoRehydration({
71
+ currentPath = '',
72
+ expandedPaths = [],
73
+ maxPaths = REPO_REHYDRATE_MAX_PATHS,
74
+ } = {}) {
75
+ const plan = repoAncestorPaths(currentPath);
76
+ const planned = new Set(plan);
77
+
78
+ const candidates = [...new Set(
79
+ (Array.isArray(expandedPaths) ? expandedPaths : [...expandedPaths])
80
+ .map((value) => String(value ?? '').trim())
81
+ .filter((value) => !!value && !planned.has(value)),
82
+ )];
83
+ candidates.sort((left, right) => {
84
+ const depthDelta = repoAncestorPaths(left).length - repoAncestorPaths(right).length;
85
+ return depthDelta !== 0 ? depthDelta : left.localeCompare(right);
86
+ });
87
+
88
+ for (const candidate of candidates) {
89
+ if (plan.length >= maxPaths) break;
90
+ const chain = repoAncestorPaths(candidate);
91
+ const parent = chain.length >= 2 ? chain[chain.length - 2] : '';
92
+ if (!planned.has(parent)) continue;
93
+ plan.push(candidate);
94
+ planned.add(candidate);
95
+ }
96
+ return plan;
97
+ }