@oar-sh/oar 0.9.19 → 0.9.21

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 (190) hide show
  1. package/.github/extensions/web-relay/polling/polling-loop.mjs +2 -26
  2. package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +2 -26
  3. package/CHANGELOG.md +529 -0
  4. package/README.md +14 -14
  5. package/bin/oar.js +392 -98
  6. package/package.json +4 -2
  7. package/server/claude-cloud-worker/claude-cloud-event-normalizer.mjs +60 -0
  8. package/server/claude-cloud-worker/claude-cloud-session-process.mjs +258 -1
  9. package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +22 -1
  10. package/server/claude-cloud-worker/claude-cloud-worker-steering.mjs +34 -0
  11. package/server/claude-worker/claude-remote-relay-tool.mjs +1 -0
  12. package/server/claude-worker/claude-session-process.mjs +10 -0
  13. package/server/claude-worker/claude-session-worker-main.mjs +8 -1
  14. package/server/claude-worker/claude-turn-publisher.mjs +163 -8
  15. package/server/claude-worker/claude-usage-totals.mjs +58 -0
  16. package/server/copilot-worker/copilot-plan-board.mjs +4 -7
  17. package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +86 -3
  18. package/server/copilot-worker/copilot-sdk-session-process.mjs +131 -5
  19. package/server/copilot-worker/copilot-sdk-session-worker.mjs +8 -1
  20. package/server/cursor-worker/cursor-session-worker.mjs +12 -1
  21. package/server/cursor-worker/cursor-turn-runner.mjs +42 -6
  22. package/server/cursor-worker/cursor-worker-steering.mjs +20 -0
  23. package/server/db-schema.mjs +18 -0
  24. package/server/deepseek-worker/deepseek-claude-backend.mjs +14 -0
  25. package/server/grok-worker/grok-background-tasks.mjs +9 -0
  26. package/server/grok-worker/grok-session-worker.mjs +12 -1
  27. package/server/grok-worker/grok-turn-runner.mjs +47 -6
  28. package/server/grok-worker/grok-worker-steering.mjs +23 -0
  29. package/server/migrations/0001-add-structured-answer.mjs +5 -7
  30. package/server/migrations/0002-recent-workspace-roots-path-key.mjs +5 -7
  31. package/server/migrations/0003-push-subscriptions.mjs +5 -7
  32. package/server/migrations/0004-steer-settle-markers.mjs +5 -7
  33. package/server/migrations/0005-remote-relays.mjs +5 -7
  34. package/server/migrations/0006-workspace-root-settings.mjs +5 -7
  35. package/server/migrations/0007-claude-cloud.mjs +5 -7
  36. package/server/migrations/0008-message-pins.mjs +5 -7
  37. package/server/migrations/0009-subagent-timeline.mjs +5 -7
  38. package/server/migrations/0010-subagent-timeline-pruning.mjs +5 -7
  39. package/server/migrations/0011-relay-event-subscriptions.mjs +5 -7
  40. package/server/migrations/0012-cross-relay-event-subscriptions.mjs +5 -7
  41. package/server/migrations/0013-previews.mjs +5 -7
  42. package/server/migrations/0014-conversation-groups.mjs +5 -7
  43. package/server/migrations/0015-conversation-group-position.mjs +5 -7
  44. package/server/migrations/0016-share-access-log.mjs +5 -7
  45. package/server/migrations/0017-conversation-history-indexes.mjs +5 -7
  46. package/server/migrations/0018-background-task-rows.mjs +5 -7
  47. package/server/migrations/0019-relay-event-subscription-options.mjs +5 -7
  48. package/server/migrations/0020-conversation-may-create-sessions.mjs +5 -7
  49. package/server/migrations/0021-conversation-group-agent-marks.mjs +5 -7
  50. package/server/migrations/0022-relay-schedules.mjs +5 -7
  51. package/server/migrations/0023-relay-asks.mjs +5 -7
  52. package/server/migrations/0024-deepseek-usage-totals.mjs +5 -7
  53. package/server/migrations/0025-deepseek-usage-requests-daily.mjs +5 -7
  54. package/server/migrations/0026-deepseek-usage-served-models.mjs +5 -7
  55. package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +5 -7
  56. package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +5 -7
  57. package/server/migrations/0029-deepseek-balance-rises.mjs +5 -7
  58. package/server/migrations/0030-deepseek-usage-actuals.mjs +5 -7
  59. package/server/migrations/0031-queue-provider.mjs +5 -7
  60. package/server/migrations/0032-runtime-session-billing-switch.mjs +5 -7
  61. package/server/migrations/0033-claude-api-usage-checkpoints.mjs +5 -7
  62. package/server/migrations/0034-claude-api-usage-totals.mjs +5 -7
  63. package/server/migrations/0035-conversation-plan-only.mjs +87 -0
  64. package/server/migrations/0036-token-usage.mjs +267 -0
  65. package/server/public/app/about-ui.js +1 -1
  66. package/server/public/app/about-view.mjs +106 -3
  67. package/server/public/app/action-confirmations.js +1 -1
  68. package/server/public/app/api-client.js +6 -3
  69. package/server/public/app/ask-user-view.js +20 -6
  70. package/server/public/app/attachments-view.js +1 -1
  71. package/server/public/app/back-guard.mjs +31 -5
  72. package/server/public/app/bootstrap.js +611 -70
  73. package/server/public/app/chat-actions-menu-state.mjs +139 -4
  74. package/server/public/app/chat-header-meta.mjs +27 -0
  75. package/server/public/app/claude-billing-toggle-ui.js +2 -1
  76. package/server/public/app/claude-cloud-conversation-ui.js +2 -2
  77. package/server/public/app/composer-control-state.mjs +12 -2
  78. package/server/public/app/composer-settings-label.mjs +90 -0
  79. package/server/public/app/composer-settings-ui.js +442 -0
  80. package/server/public/app/context-menu.mjs +36 -5
  81. package/server/public/app/context-usage-view.mjs +16 -0
  82. package/server/public/app/conversation-deep-link.mjs +15 -11
  83. package/server/public/app/conversation-group-drag.mjs +12 -2
  84. package/server/public/app/conversation-group-order.mjs +9 -2
  85. package/server/public/app/conversation-group-state.mjs +47 -10
  86. package/server/public/app/conversation-origin-ui.js +17 -1
  87. package/server/public/app/conversation-view.js +20 -2
  88. package/server/public/app/cwd-menu-placement.mjs +18 -6
  89. package/server/public/app/cwd-picker.js +36 -6
  90. package/server/public/app/emoji-view.js +1 -1
  91. package/server/public/app/font-scaling.js +26 -0
  92. package/server/public/app/header-priority-actions.mjs +234 -0
  93. package/server/public/app/hidden-messages-view.mjs +4 -0
  94. package/server/public/app/inbox-ui.js +50 -2
  95. package/server/public/app/inbox-view.mjs +141 -5
  96. package/server/public/app/journal-view.js +311 -58
  97. package/server/public/app/phone-landscape-ui.js +225 -0
  98. package/server/public/app/phone-landscape.mjs +99 -0
  99. package/server/public/app/phone-typing-state.mjs +104 -0
  100. package/server/public/app/pinned-messages-view.mjs +5 -0
  101. package/server/public/app/plan-board-shared.mjs +49 -0
  102. package/server/public/app/plan-usage-view.mjs +1 -1
  103. package/server/public/app/push-settings.js +2 -2
  104. package/server/public/app/pwa-install.js +24 -3
  105. package/server/public/app/relay-board-inline.mjs +4 -1
  106. package/server/public/app/relay-board-view.js +38 -10
  107. package/server/public/app/relay-event-subscriptions-ui.js +71 -22
  108. package/server/public/app/relay-event-view.mjs +42 -0
  109. package/server/public/app/relay-restart-ui.js +2 -2
  110. package/server/public/app/relay-schedule-view.mjs +32 -6
  111. package/server/public/app/relay-schedules-ui.js +69 -20
  112. package/server/public/app/remote-relay-origin-view.mjs +41 -23
  113. package/server/public/app/session-history-ui.js +250 -0
  114. package/server/public/app/session-history.mjs +599 -0
  115. package/server/public/app/settings-modal.js +61 -6
  116. package/server/public/app/settings-tabs.js +12 -0
  117. package/server/public/app/share-state.js +1 -1
  118. package/server/public/app/shared-conversations-settings.js +2 -2
  119. package/server/public/app/socket-handlers.js +15 -4
  120. package/server/public/app/status-view.mjs +18 -2
  121. package/server/public/app/store.js +87 -16
  122. package/server/public/app/subagent-viewer.mjs +27 -2
  123. package/server/public/app/tab-strip-fade.mjs +101 -0
  124. package/server/public/app/tap-actions.js +66 -0
  125. package/server/public/app/usage-limit-ui.js +63 -2
  126. package/server/public/app/viewer-history.mjs +13 -2
  127. package/server/public/index.html +2258 -1585
  128. package/server/public/sw.js +8 -0
  129. package/server/relay-heap-guard.mjs +14 -1
  130. package/server/relay-self-restart.mjs +83 -2
  131. package/server/relay-tools.md +5 -3
  132. package/server/repositories/message-repository.mjs +54 -1
  133. package/server/repositories/remote-relay-repository.mjs +10 -0
  134. package/server/repositories/session-repository.mjs +25 -1
  135. package/server/routes/claude-cloud-routes.mjs +19 -0
  136. package/server/routes/messages-routes.mjs +144 -1
  137. package/server/routes/relay-ask-routes.mjs +138 -8
  138. package/server/routes/relay-board-routes.mjs +55 -40
  139. package/server/routes/sessions-routes.mjs +144 -14
  140. package/server/server-runtime.mjs +322 -51
  141. package/server/services/about-service.mjs +81 -53
  142. package/server/services/cache-rebuild-service.mjs +7 -0
  143. package/server/services/claude-billing-swap-service.mjs +7 -20
  144. package/server/services/claude-cloud-session-service.mjs +47 -5
  145. package/server/services/conversation-preferences-service.mjs +31 -0
  146. package/server/services/database-file.mjs +413 -0
  147. package/server/services/db-backup-service.mjs +809 -0
  148. package/server/services/deepseek-usage-import.mjs +6 -9
  149. package/server/services/delete-archive-service.mjs +3 -0
  150. package/server/services/earlier-database-service.mjs +69 -126
  151. package/server/services/oar-cli-helpers.mjs +214 -12
  152. package/server/services/oar-cli-menu.mjs +1 -0
  153. package/server/services/oar-state-migration-service.mjs +33 -13
  154. package/server/services/package-data-rescue.mjs +4 -2
  155. package/server/services/plan-usage-claude-api.mjs +14 -15
  156. package/server/services/plan-usage-contract.mjs +6 -2
  157. package/server/services/plan-usage-deepseek.mjs +1 -1
  158. package/server/services/plan-usage-tokens.mjs +248 -0
  159. package/server/services/push-dispatch-service.mjs +134 -9
  160. package/server/services/relay-cli-launcher-service.mjs +3 -0
  161. package/server/services/relay-event-service.mjs +30 -0
  162. package/server/services/relay-question-context.mjs +11 -0
  163. package/server/services/relay-restart-picture.mjs +67 -22
  164. package/server/services/remote-inbox-watch-service.mjs +204 -0
  165. package/server/services/remote-relay-caller-context.mjs +80 -7
  166. package/server/services/remote-relay-dispatcher.mjs +101 -19
  167. package/server/services/restart-resume-service.mjs +5 -2
  168. package/server/services/session-activity-tracker.mjs +1 -1
  169. package/server/services/session-launch-mutex-service.mjs +23 -0
  170. package/server/services/session-worker-launch-service.mjs +12 -3
  171. package/server/services/session-worker-registry-service.mjs +36 -0
  172. package/server/services/session-worker-stop-service.mjs +27 -6
  173. package/server/services/session-worker-websocket-service.mjs +40 -16
  174. package/server/services/token-usage-service.mjs +430 -0
  175. package/server/services/update-install-service.mjs +13 -0
  176. package/server/services/usage-limit-pause-service.mjs +8 -1
  177. package/server/services/worker-recycle-service.mjs +772 -0
  178. package/server/services/worker-replacement.mjs +197 -0
  179. package/server/services/worker-scope.mjs +77 -0
  180. package/shared/build-id.mjs +128 -0
  181. package/shared/plan-board-actions.mjs +130 -0
  182. package/shared/plan-only.mjs +43 -0
  183. package/shared/relay-asks.mjs +3 -0
  184. package/shared/relay-events.mjs +38 -4
  185. package/shared/remote-relay-contract.mjs +66 -11
  186. package/shared/turn-token-usage.mjs +144 -0
  187. package/shared/worker-runtime/heartbeat.mjs +5 -0
  188. package/shared/worker-runtime/simple-steering-state.mjs +38 -0
  189. package/shared/worker-runtime/worker-websocket-link.mjs +23 -0
  190. package/shared/worker-runtime/working-directory.mjs +1 -1
@@ -29,6 +29,8 @@ let currentConversationId = '';
29
29
  let currentProviderType = '';
30
30
  let actionInFlight = false;
31
31
  let wakeTimer = null;
32
+ // The report whose one-line warning was tapped open (its dismissedKey).
33
+ let expandedKey = '';
32
34
 
33
35
  function notifyBilling(conversationId) {
34
36
  const id = String(conversationId || '').trim();
@@ -88,6 +90,30 @@ export function usageLimitAccountText(state, nowMs = Date.now()) {
88
90
  return `Claude usage at ${Math.round(utilization * 100)} % of the ${label} — resets ${resets}`;
89
91
  }
90
92
 
93
+ /** "04:00" today, "Tue 04:00" on another day: the one-line banner's time. */
94
+ export function formatUsageLimitShortTime(iso, nowMs = Date.now()) {
95
+ const at = new Date(String(iso || ''));
96
+ if (Number.isNaN(at.getTime())) return '';
97
+ const clock = at.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
98
+ if (new Date(nowMs).toDateString() === at.toDateString()) return clock;
99
+ return `${at.toLocaleDateString([], { weekday: 'short' })} ${clock}`;
100
+ }
101
+
102
+ /**
103
+ * The account's line in one short line, for the compact banner:
104
+ * "⚠ Claude 94 % weekly · resets Tue 04:00", or at the limit
105
+ * "⚠ Claude weekly limit reached · resets Tue 04:00". '' when
106
+ * usageLimitAccountText says nothing.
107
+ */
108
+ export function usageLimitAccountShortText(state, nowMs = Date.now()) {
109
+ if (!usageLimitAccountText(state, nowMs)) return '';
110
+ const label = String(state.label || 'usage limit');
111
+ const resets = formatUsageLimitShortTime(state.resetsAt, nowMs);
112
+ if (state.status === 'rejected') return `⚠ Claude ${label} reached · resets ${resets}`;
113
+ const limitWindow = label.replace(/\s*limit$/i, '').trim() || label;
114
+ return `⚠ Claude ${Math.round(Number(state.utilization) * 100)} % ${limitWindow} · resets ${resets}`;
115
+ }
116
+
91
117
  /**
92
118
  * Whether the usage-limit note offers to move the chat to the API key: a
93
119
  * subscription chat (never a cloud one) whose relay says the switch is
@@ -201,6 +227,8 @@ export function renderUsageLimitBanner(nowMs = Date.now()) {
201
227
  const banner = document.getElementById(BANNER_ID);
202
228
  if (!banner) return;
203
229
  const children = [];
230
+ let compact = false;
231
+ let expanded = false;
204
232
  const pause = activeUsageLimitPause(currentConversationId, nowMs);
205
233
  if (pause) {
206
234
  const text = document.createElement('span');
@@ -225,14 +253,46 @@ export function renderUsageLimitBanner(nowMs = Date.now()) {
225
253
  ? usageLimitAccountText(account, nowMs)
226
254
  : '';
227
255
  if (line) {
256
+ // One line with the full sentence in the title; a tap on it opens the
257
+ // full sentence until the report changes.
258
+ if (expandedKey && expandedKey !== dismissedKey(account)) expandedKey = '';
259
+ expanded = expandedKey !== '' && expandedKey === dismissedKey(account);
228
260
  const text = document.createElement('span');
229
261
  text.className = 'usage-limit-text';
230
- text.textContent = line;
231
- children.push(text, button('Hide', 'usage-limit-dismiss', async () => dismissWarning()));
262
+ text.textContent = expanded ? line : (usageLimitAccountShortText(account, nowMs) || line);
263
+ text.title = line;
264
+ text.setAttribute('role', 'button');
265
+ text.tabIndex = 0;
266
+ text.setAttribute('aria-expanded', expanded ? 'true' : 'false');
267
+ const toggle = (event) => {
268
+ event.preventDefault();
269
+ expandedKey = expanded ? '' : dismissedKey(account);
270
+ renderUsageLimitBanner();
271
+ };
272
+ text.addEventListener('click', toggle);
273
+ text.addEventListener('keydown', (event) => {
274
+ if (event.key === 'Enter' || event.key === ' ') toggle(event);
275
+ });
276
+ const hide = button('✕', 'usage-limit-dismiss', async () => dismissWarning());
277
+ hide.title = 'Hide';
278
+ hide.setAttribute('aria-label', 'Hide');
279
+ children.push(text, hide);
232
280
  banner.dataset.state = account.status === 'rejected' ? 'reached' : 'warning';
281
+ compact = true;
233
282
  }
234
283
  }
284
+ banner.classList.toggle('usage-limit-compact', compact);
285
+ banner.classList.toggle('usage-limit-expanded', compact && expanded);
286
+ // The banner is drawn anew: the control that had the focus (the line's
287
+ // toggle, a button) gets it back in its new node, found by its class.
288
+ const focused = typeof document.activeElement?.className === 'string' && banner.contains(document.activeElement)
289
+ ? document.activeElement.className
290
+ : '';
235
291
  banner.replaceChildren(...children);
292
+ if (focused) {
293
+ const again = children.find((child) => child.className === focused);
294
+ try { again?.focus({ preventScroll: true }); } catch {}
295
+ }
236
296
  banner.classList.toggle('visible', children.length > 0);
237
297
  banner.hidden = children.length === 0;
238
298
  if (!children.length) delete banner.dataset.state;
@@ -350,6 +410,7 @@ export function __resetUsageLimitUiForTests() {
350
410
  currentConversationId = '';
351
411
  currentProviderType = '';
352
412
  actionInFlight = false;
413
+ expandedKey = '';
353
414
  if (wakeTimer) { clearTimeout(wakeTimer); wakeTimer = null; }
354
415
  try { localStorage.removeItem(DISMISSED_KEY); } catch {}
355
416
  renderUsageLimitBanner();
@@ -4,7 +4,11 @@
4
4
  // and the pop closes the viewer. Closing by any other means (the × button,
5
5
  // Escape, a swipe down) pops that entry itself so history is left as it
6
6
  // was, and the pop that follows is recognised as our own and ignored. The
7
- // address never changes: the entry carries only its state.
7
+ // address never changes: the entry carries only its state, its place in the
8
+ // stack and the visit's marks of the entry below (session-history.mjs, the
9
+ // back guard).
10
+
11
+ import { HISTORY_POSITION, inheritedVisitMarks } from './back-guard.mjs';
8
12
 
9
13
  export const VIEWER_HISTORY_MARK = 'oarFileViewer';
10
14
  const OWN_POP_WINDOW_MS = 1500;
@@ -18,7 +22,11 @@ export function createViewerHistory({ history, onBack, now = () => Date.now(), m
18
22
  /** The viewer came up: one entry, however many files it then shows. */
19
23
  opened() {
20
24
  if (state.pushed) return;
21
- history.pushState({ [mark]: true }, '');
25
+ const below = history.state;
26
+ const entry = { ...inheritedVisitMarks(below), [mark]: true };
27
+ const position = below?.[HISTORY_POSITION];
28
+ if (Number.isFinite(position)) entry[HISTORY_POSITION] = position + 1;
29
+ history.pushState(entry, '');
22
30
  state.pushed = true;
23
31
  },
24
32
  /** The viewer went away by itself: take its entry back out of history. */
@@ -38,6 +46,9 @@ export function createViewerHistory({ history, onBack, now = () => Date.now(), m
38
46
  }
39
47
  state.ownPopAt = 0;
40
48
  if (!state.pushed) return;
49
+ // Our entry is still the current one: what was popped lay above it
50
+ // (a dialog opened over the viewer), the viewer stays.
51
+ if (history.state?.[mark]) return;
41
52
  state.pushed = false;
42
53
  onBack?.();
43
54
  },