@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,83 @@
1
+ /**
2
+ * The relay's ask_user custom tool for the Cursor worker. SDK-free: the
3
+ * adapter passes the returned `{ description, inputSchema, execute }` into
4
+ * `customTools.ask_user`, and all relay traffic goes through the injected
5
+ * shared ask-user bridge.
6
+ */
7
+
8
+ export const ASK_USER_TOOL_DESCRIPTION =
9
+ 'Ask the user one or more clarifying questions and wait for their answers. '
10
+ + 'This is the ONLY way to ask the user anything: a question written as plain '
11
+ + 'reply text is NOT shown interactively and ends the turn unanswered. Call '
12
+ + 'this tool whenever user input would materially change the result, before '
13
+ + 'making assumptions. Provide 2-4 concise options per question; the user can '
14
+ + 'always answer in free text instead. This tool blocks until the user '
15
+ + 'responds; on timeout it returns an instruction to continue under the '
16
+ + 'current relay mode.';
17
+
18
+ export const ASK_USER_INPUT_SCHEMA = {
19
+ type: 'object',
20
+ properties: {
21
+ questions: {
22
+ type: 'array',
23
+ minItems: 1,
24
+ maxItems: 4,
25
+ items: {
26
+ type: 'object',
27
+ properties: {
28
+ question: { type: 'string' },
29
+ header: { type: 'string' },
30
+ multiSelect: { type: 'boolean', default: false },
31
+ options: {
32
+ type: 'array',
33
+ minItems: 2,
34
+ maxItems: 6,
35
+ items: {
36
+ type: 'object',
37
+ properties: {
38
+ label: { type: 'string' },
39
+ description: { type: 'string' },
40
+ },
41
+ required: ['label'],
42
+ },
43
+ },
44
+ },
45
+ required: ['question', 'options'],
46
+ },
47
+ },
48
+ },
49
+ required: ['questions'],
50
+ };
51
+
52
+ export function createAskUserTool({ bridge, getAbortSignal, dbg = () => {} } = {}) {
53
+ async function execute(args) {
54
+ const signal = getAbortSignal?.();
55
+ try {
56
+ const { answers, timedOut } = await bridge.handleAskUserQuestion(args, { signal });
57
+ const entries = Object.entries(answers || {});
58
+ const text = entries.length
59
+ ? entries.map(([question, answer]) => `Q: ${question}\nA: ${answer}`).join('\n\n')
60
+ : 'No user response.';
61
+ return {
62
+ structuredContent: { answers, timedOut },
63
+ content: [{ type: 'text', text }],
64
+ };
65
+ } catch (error) {
66
+ // A tool exception must never kill the run.
67
+ const message = error?.message || String(error);
68
+ dbg('ask_user bridge failed', message);
69
+ return {
70
+ content: [{
71
+ type: 'text',
72
+ text: `ask_user failed: ${message}. Continue without user input.`,
73
+ }],
74
+ };
75
+ }
76
+ }
77
+ return {
78
+ name: 'ask_user',
79
+ description: ASK_USER_TOOL_DESCRIPTION,
80
+ inputSchema: ASK_USER_INPUT_SCHEMA,
81
+ execute,
82
+ };
83
+ }
@@ -0,0 +1,88 @@
1
+ import fs from 'fs';
2
+
3
+ // Very large base64 image payloads risk rejection; larger images fall back to
4
+ // a path reference so the agent can read them from disk instead.
5
+ export const MAX_INLINE_IMAGE_BYTES = 5 * 1024 * 1024;
6
+
7
+ function isImageAttachment(att) {
8
+ return String(att?.type || '').toLowerCase().startsWith('image/');
9
+ }
10
+
11
+ function imageFromDataUrl(att) {
12
+ const dataUrl = String(att?.dataUrl || '').trim();
13
+ if (!dataUrl.startsWith('data:')) return null;
14
+ const match = dataUrl.match(/^data:([^;,]+);base64,(.+)$/i);
15
+ if (!match) return null;
16
+ const mimeType = String(match[1] || '').trim().toLowerCase();
17
+ const data = String(match[2] || '').trim();
18
+ if (!mimeType.startsWith('image/') || !data) return null;
19
+ return { data, mimeType };
20
+ }
21
+
22
+ /**
23
+ * Build the `{ text, images }` payload for `agent.send()` from a relay
24
+ * message. Mirrors the Claude worker's attachment handling: images small
25
+ * enough to embed become base64 `{ data, mimeType }` entries; everything else
26
+ * (non-images, oversized or unreadable images) becomes a prompt note line with
27
+ * the absolute path so the agent's file tools can open it directly.
28
+ *
29
+ * Text composition order: user text, note lines, server-provided
30
+ * `attachmentPromptContext`, joined by blank lines.
31
+ */
32
+ export function buildCursorUserMessage(message, {
33
+ fsImpl = fs,
34
+ maxInlineImageBytes = MAX_INLINE_IMAGE_BYTES,
35
+ } = {}) {
36
+ const text = String(message?.text || '').trim();
37
+ const input = Array.isArray(message?.attachments) ? message.attachments : [];
38
+ const images = [];
39
+ const noteLines = [];
40
+
41
+ for (const att of input) {
42
+ if (!att || typeof att !== 'object') continue;
43
+ const name = String(att.name || 'attachment').trim() || 'attachment';
44
+ const mime = String(att.type || 'application/octet-stream').trim() || 'application/octet-stream';
45
+ const filePath = String(att.path || '').trim();
46
+
47
+ if (isImageAttachment(att)) {
48
+ let embedded = false;
49
+ if (filePath && fsImpl.existsSync(filePath)) {
50
+ try {
51
+ const bytes = fsImpl.readFileSync(filePath);
52
+ if (Buffer.isBuffer(bytes) && bytes.length && bytes.length <= maxInlineImageBytes) {
53
+ images.push({
54
+ data: bytes.toString('base64'),
55
+ mimeType: mime.toLowerCase().startsWith('image/') ? mime.toLowerCase() : 'image/png',
56
+ });
57
+ embedded = true;
58
+ }
59
+ } catch {
60
+ embedded = false;
61
+ }
62
+ }
63
+ if (!embedded) {
64
+ const fromDataUrl = imageFromDataUrl(att);
65
+ if (fromDataUrl) {
66
+ images.push(fromDataUrl);
67
+ embedded = true;
68
+ }
69
+ }
70
+ if (embedded) {
71
+ noteLines.push(`Attached image "${name}" (${mime}) is embedded in this message.`);
72
+ } else if (filePath) {
73
+ noteLines.push(`Attached image "${name}" (${mime}): ${filePath}`);
74
+ }
75
+ continue;
76
+ }
77
+
78
+ if (filePath && fsImpl.existsSync(filePath)) {
79
+ noteLines.push(`Attached file "${name}" (${mime}): ${filePath}`);
80
+ }
81
+ }
82
+
83
+ const attachmentPromptContext = String(message?.attachmentPromptContext || '').trim();
84
+ const textParts = [text];
85
+ if (noteLines.length) textParts.push(noteLines.join('\n'));
86
+ if (attachmentPromptContext) textParts.push(attachmentPromptContext);
87
+ return { text: textParts.filter(Boolean).join('\n\n'), images };
88
+ }
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Maps the Cursor SDK's TokenUsage onto the payload shape the relay already
3
+ * persists for Claude sessions, so `normalizeClaudeContextUsage` and
4
+ * `buildClaudeContextSnapshot` (server/services/claude-context-usage.mjs)
5
+ * render it unchanged.
6
+ *
7
+ * The Cursor SDK reports no per-category breakdown and no auto-compact
8
+ * settings, so `categories` is always empty and the skills/autoCompact keys
9
+ * are omitted entirely.
10
+ */
11
+
12
+ // Percentages above this are impossible occupancy readings; the worker clamps
13
+ // them so the stored blob never claims more than a full window.
14
+ const MAX_PERCENT = 100;
15
+
16
+ function tokenCount(value) {
17
+ if (value === null || value === undefined || value === '') return null;
18
+ const n = Number(value);
19
+ return Number.isFinite(n) ? Math.round(n) : null;
20
+ }
21
+
22
+ export function buildCursorContextUsage({
23
+ usage,
24
+ model = '',
25
+ contextWindow = null,
26
+ modelCallCount = 1,
27
+ } = {}) {
28
+ if (!usage || typeof usage !== 'object') return null;
29
+
30
+ const inputTokens = tokenCount(usage.inputTokens);
31
+ const outputTokens = tokenCount(usage.outputTokens);
32
+ const cacheReadTokens = tokenCount(usage.cacheReadTokens);
33
+ const cacheWriteTokens = tokenCount(usage.cacheWriteTokens);
34
+ if (inputTokens === null && outputTokens === null
35
+ && cacheReadTokens === null && cacheWriteTokens === null) {
36
+ return null;
37
+ }
38
+
39
+ // The SDK's turn usage aggregates EVERY model call in the turn, and each
40
+ // call re-sends the whole context — so input + cache reads + cache writes is
41
+ // roughly `calls ×` the real occupancy (observed 10× the window on long
42
+ // agentic turns; the SDK's cumulative `totalTokens` is even further off and
43
+ // must not be used). Divide the prompt-side aggregate by the model-call
44
+ // count for a per-call average: an estimate that slightly undershoots the
45
+ // final call's context instead of pinning the gauge at 100%.
46
+ const calls = Math.max(1, Math.round(Number(modelCallCount) || 1));
47
+ const promptAggregate = (inputTokens || 0) + (cacheReadTokens || 0) + (cacheWriteTokens || 0);
48
+ const totalTokens = Math.round(promptAggregate / calls) + (outputTokens || 0);
49
+ const estimateKind = calls > 1 ? 'cursor-per-call-average' : null;
50
+ const maxTokens = (typeof contextWindow === 'number' && Number.isFinite(contextWindow)
51
+ && contextWindow > 0)
52
+ ? Math.round(contextWindow)
53
+ : null;
54
+
55
+ const contextUsage = {
56
+ model,
57
+ totalTokens,
58
+ ...(maxTokens !== null
59
+ ? {
60
+ maxTokens,
61
+ percentage: Math.min(MAX_PERCENT, Math.round((totalTokens / maxTokens) * 10000) / 100),
62
+ }
63
+ : {}),
64
+ ...(estimateKind ? { estimateKind } : {}),
65
+ categories: [],
66
+ apiUsage: {
67
+ input_tokens: inputTokens || 0,
68
+ output_tokens: outputTokens || 0,
69
+ cache_read_input_tokens: cacheReadTokens || 0,
70
+ cache_creation_input_tokens: cacheWriteTokens || 0,
71
+ },
72
+ };
73
+
74
+ const modelUsage = {
75
+ [model]: {
76
+ ...(maxTokens !== null ? { contextWindow: maxTokens } : {}),
77
+ inputTokens: inputTokens || 0,
78
+ outputTokens: outputTokens || 0,
79
+ cacheReadInputTokens: cacheReadTokens || 0,
80
+ cacheCreationInputTokens: cacheWriteTokens || 0,
81
+ },
82
+ };
83
+
84
+ return { contextUsage, modelUsage };
85
+ }
@@ -0,0 +1,44 @@
1
+ /**
2
+ * The relay's preview custom tool for the Cursor worker. SDK-free: the adapter
3
+ * passes the returned `{ description, inputSchema, execute }` into
4
+ * `customTools.preview`, and every verb runs through the shared preview core
5
+ * against the worker's injected relay API helper.
6
+ */
7
+
8
+ import {
9
+ PREVIEW_TOOL_DESCRIPTION,
10
+ PREVIEW_TOOL_INPUT_SCHEMA,
11
+ PREVIEW_TOOL_NAME,
12
+ executePreviewTool,
13
+ } from '../../shared/preview-tool-core.mjs';
14
+
15
+ export function createPreviewTool({ api, getConversationId = () => '', dbg = () => {} } = {}) {
16
+ async function execute(args) {
17
+ try {
18
+ const result = await executePreviewTool(args, {
19
+ api,
20
+ conversationId: String(getConversationId() || ''),
21
+ });
22
+ return {
23
+ structuredContent: result,
24
+ content: [{ type: 'text', text: JSON.stringify(result) }],
25
+ };
26
+ } catch (error) {
27
+ // A tool exception must never kill the run.
28
+ const message = error?.message || String(error);
29
+ dbg('preview tool failed', message);
30
+ return {
31
+ content: [{
32
+ type: 'text',
33
+ text: `preview failed: ${message}. Continue without publishing a preview.`,
34
+ }],
35
+ };
36
+ }
37
+ }
38
+ return {
39
+ name: PREVIEW_TOOL_NAME,
40
+ description: PREVIEW_TOOL_DESCRIPTION,
41
+ inputSchema: PREVIEW_TOOL_INPUT_SCHEMA,
42
+ execute,
43
+ };
44
+ }