@oar-sh/oar 0.9.20 → 0.9.22

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 (131) hide show
  1. package/CHANGELOG.md +325 -0
  2. package/README.md +13 -13
  3. package/bin/oar.js +2 -2
  4. package/package.json +1 -1
  5. package/server/claude-cloud-worker/claude-cloud-session-process.mjs +231 -1
  6. package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +31 -2
  7. package/server/claude-cloud-worker/claude-cloud-worker-steering.mjs +34 -0
  8. package/server/claude-worker/claude-sdk-adapter.mjs +149 -4
  9. package/server/claude-worker/claude-session-process.mjs +16 -4
  10. package/server/claude-worker/claude-session-worker-main.mjs +18 -2
  11. package/server/copilot-worker/copilot-sdk-session-process.mjs +75 -1
  12. package/server/copilot-worker/copilot-sdk-session-worker.mjs +18 -2
  13. package/server/cursor-worker/cursor-session-worker.mjs +21 -2
  14. package/server/cursor-worker/cursor-worker-steering.mjs +20 -0
  15. package/server/db-schema.mjs +19 -0
  16. package/server/grok-worker/grok-background-tasks.mjs +10 -0
  17. package/server/grok-worker/grok-session-worker.mjs +22 -2
  18. package/server/grok-worker/grok-turn-runner.mjs +4 -0
  19. package/server/grok-worker/grok-worker-steering.mjs +23 -0
  20. package/server/migrations/0037-relay-asks-queue-id.mjs +66 -0
  21. package/server/migrations/0038-turn-seq.mjs +92 -0
  22. package/server/public/app/about-ui.js +1 -1
  23. package/server/public/app/about-view.mjs +19 -0
  24. package/server/public/app/action-confirmations.js +1 -1
  25. package/server/public/app/activity-replay-state.mjs +160 -42
  26. package/server/public/app/api-client.js +11 -2
  27. package/server/public/app/ask-user-view.js +24 -9
  28. package/server/public/app/attachments-view.js +1 -1
  29. package/server/public/app/back-guard.mjs +31 -5
  30. package/server/public/app/bootstrap.js +618 -72
  31. package/server/public/app/chat-actions-menu-state.mjs +141 -4
  32. package/server/public/app/chat-header-meta.mjs +27 -0
  33. package/server/public/app/claude-billing-toggle-ui.js +2 -1
  34. package/server/public/app/claude-cloud-conversation-ui.js +2 -2
  35. package/server/public/app/composer-settings-label.mjs +90 -0
  36. package/server/public/app/composer-settings-ui.js +442 -0
  37. package/server/public/app/context-menu.mjs +36 -5
  38. package/server/public/app/context-usage-view.mjs +68 -0
  39. package/server/public/app/conversation-deep-link.mjs +15 -11
  40. package/server/public/app/conversation-group-drag.mjs +12 -2
  41. package/server/public/app/conversation-group-order.mjs +9 -2
  42. package/server/public/app/conversation-group-state.mjs +47 -10
  43. package/server/public/app/conversation-origin-ui.js +17 -1
  44. package/server/public/app/conversation-view.js +309 -49
  45. package/server/public/app/cwd-menu-placement.mjs +18 -6
  46. package/server/public/app/cwd-picker.js +36 -6
  47. package/server/public/app/emoji-view.js +1 -1
  48. package/server/public/app/font-scaling.js +26 -0
  49. package/server/public/app/header-priority-actions.mjs +238 -0
  50. package/server/public/app/held-details.mjs +202 -0
  51. package/server/public/app/hidden-messages-view.mjs +4 -0
  52. package/server/public/app/inbox-ui.js +88 -81
  53. package/server/public/app/inbox-view.mjs +35 -19
  54. package/server/public/app/journal-view.js +328 -58
  55. package/server/public/app/live-activity-cap.mjs +103 -0
  56. package/server/public/app/phone-landscape-ui.js +225 -0
  57. package/server/public/app/phone-landscape.mjs +99 -0
  58. package/server/public/app/phone-typing-state.mjs +104 -0
  59. package/server/public/app/pinned-messages-view.mjs +5 -0
  60. package/server/public/app/push-settings.js +2 -2
  61. package/server/public/app/pwa-install.js +24 -3
  62. package/server/public/app/questions-panel-ui.js +634 -0
  63. package/server/public/app/questions-stub-view.mjs +40 -0
  64. package/server/public/app/questions-view.mjs +89 -0
  65. package/server/public/app/relay-board-view.js +12 -2
  66. package/server/public/app/relay-event-subscriptions-ui.js +71 -22
  67. package/server/public/app/relay-event-view.mjs +38 -0
  68. package/server/public/app/relay-restart-ui.js +2 -2
  69. package/server/public/app/relay-schedule-view.mjs +32 -6
  70. package/server/public/app/relay-schedules-ui.js +69 -20
  71. package/server/public/app/relay-thoughts.mjs +15 -1
  72. package/server/public/app/remote-relay-origin-view.mjs +41 -23
  73. package/server/public/app/session-history-ui.js +251 -0
  74. package/server/public/app/session-history.mjs +599 -0
  75. package/server/public/app/settings-modal.js +67 -6
  76. package/server/public/app/settings-tabs.js +12 -0
  77. package/server/public/app/share-state.js +1 -1
  78. package/server/public/app/shared-conversations-settings.js +2 -2
  79. package/server/public/app/socket-handlers.js +47 -13
  80. package/server/public/app/status-view.mjs +18 -2
  81. package/server/public/app/store.js +87 -16
  82. package/server/public/app/stream-view-preference.mjs +60 -0
  83. package/server/public/app/subagent-fold.mjs +6 -3
  84. package/server/public/app/subagent-timeline-view.mjs +12 -5
  85. package/server/public/app/subagent-viewer.mjs +35 -8
  86. package/server/public/app/tab-strip-fade.mjs +101 -0
  87. package/server/public/app/tap-actions.js +66 -0
  88. package/server/public/app/thoughts-preference.mjs +79 -0
  89. package/server/public/app/turn-stream-view.mjs +124 -0
  90. package/server/public/app/usage-limit-ui.js +63 -2
  91. package/server/public/app/viewer-history.mjs +13 -2
  92. package/server/public/index.html +2603 -1767
  93. package/server/relay-tools.md +1 -0
  94. package/server/repositories/message-repository.mjs +33 -0
  95. package/server/repositories/question-repository.mjs +51 -7
  96. package/server/repositories/relay-turn-rows.mjs +56 -0
  97. package/server/repositories/session-repository.mjs +19 -0
  98. package/server/routes/ask-user-routes.mjs +34 -5
  99. package/server/routes/claude-cloud-routes.mjs +19 -0
  100. package/server/routes/messages-routes.mjs +92 -15
  101. package/server/routes/relay-ask-routes.mjs +15 -5
  102. package/server/routes/sessions-routes.mjs +117 -12
  103. package/server/server-runtime.mjs +367 -170
  104. package/server/services/about-service.mjs +22 -53
  105. package/server/services/claude-billing-swap-service.mjs +7 -20
  106. package/server/services/claude-cloud-session-service.mjs +47 -5
  107. package/server/services/cloudflared-tunnel-service.mjs +7 -0
  108. package/server/services/idle-timers.mjs +184 -0
  109. package/server/services/queue-wake-hook.mjs +145 -0
  110. package/server/services/queue-waker.mjs +127 -0
  111. package/server/services/relay-ask-service.mjs +64 -5
  112. package/server/services/relay-event-service.mjs +4 -1
  113. package/server/services/remote-relay-dispatcher.mjs +84 -18
  114. package/server/services/session-activity-tracker.mjs +1 -1
  115. package/server/services/session-history-refresh-service.mjs +4 -0
  116. package/server/services/session-launch-mutex-service.mjs +23 -0
  117. package/server/services/session-worker-registry-service.mjs +36 -0
  118. package/server/services/session-worker-supervisor-service.mjs +36 -0
  119. package/server/services/session-worker-websocket-service.mjs +183 -31
  120. package/server/services/ssh-tunnel-manager-service.mjs +7 -0
  121. package/server/services/worker-recycle-service.mjs +772 -0
  122. package/server/services/worker-replacement.mjs +197 -0
  123. package/shared/ask-user-bridge.mjs +9 -6
  124. package/shared/build-id.mjs +128 -0
  125. package/shared/relay-asks.mjs +10 -0
  126. package/shared/relay-events.mjs +1 -0
  127. package/shared/worker-runtime/heartbeat.mjs +31 -0
  128. package/shared/worker-runtime/simple-steering-state.mjs +38 -0
  129. package/shared/worker-runtime/worker-keepalive.mjs +108 -0
  130. package/shared/worker-runtime/worker-websocket-link.mjs +44 -10
  131. package/shared/worker-runtime/working-directory.mjs +1 -1
@@ -211,6 +211,9 @@ export function pageConversationRows(rows, groups = null, { limit = 40, beforeCo
211
211
  * match a filter (a group shows when any row matches: its head as context
212
212
  * with the matching members, or whole when the head or its name matches).
213
213
  *
214
+ * `keepVisibleId` is the open conversation: a member of a folded group with
215
+ * that id still shows under its head, as `{ kind: 'member', kept: true }`.
216
+ *
214
217
  * With `emptyGroups`, an owner's group an agent emptied (`keptEmptyAt`, no
215
218
  * row here) shows too: a head item without members (`group.empty`), placed
216
219
  * by the time it was emptied; under a filter only when its name matches.
@@ -220,8 +223,9 @@ export function arrangeConversationList(rows, groups = null, {
220
223
  matches = null,
221
224
  groupNameMatches = null,
222
225
  emptyGroups = false,
226
+ keepVisibleId = null,
223
227
  } = {}) {
224
- const items = arrangeGroupedRows(rows, groups, { isOpen, matches, groupNameMatches });
228
+ const items = arrangeGroupedRows(rows, groups, { isOpen, matches, groupNameMatches, keepVisibleId: toText(keepVisibleId) });
225
229
  if (!emptyGroups || !groups) return items;
226
230
  const present = new Set((Array.isArray(rows) ? rows : []).map(rowGroupId).filter(Boolean));
227
231
  const entries = typeof groups.entries === 'function' && typeof groups.get === 'function' ? [...groups.entries()] : Object.entries(groups);
@@ -250,7 +254,7 @@ export function arrangeConversationList(rows, groups = null, {
250
254
  return out;
251
255
  }
252
256
 
253
- function arrangeGroupedRows(rows, groups, { isOpen, matches, groupNameMatches }) {
257
+ function arrangeGroupedRows(rows, groups, { isOpen, matches, groupNameMatches, keepVisibleId = '' }) {
254
258
  const ordered = orderConversationRows(rows, groups);
255
259
  const activity = groupActivityTimes(ordered);
256
260
  const items = [];
@@ -294,6 +298,9 @@ function arrangeGroupedRows(rows, groups, { isOpen, matches, groupNameMatches })
294
298
  items.push({ kind: 'head', conversation: headRow, group });
295
299
  if (group.open) {
296
300
  for (const member of shownMembers) items.push({ kind: 'member', conversation: member, group });
301
+ } else if (keepVisibleId) {
302
+ const kept = shownMembers.find((member) => rowId(member) === keepVisibleId);
303
+ if (kept) items.push({ kind: 'member', conversation: kept, group, kept: true });
297
304
  }
298
305
  }
299
306
  return items;
@@ -42,12 +42,24 @@ export function summarizeGroupMembers(states = []) {
42
42
  export function groupSummaryText(summary) {
43
43
  const count = Number(summary?.count || 0);
44
44
  if (!count) return '';
45
+ const activity = groupActivityText(summary);
46
+ if (!activity) return groupSessionCountText(count);
47
+ return `${count} · ${activity}`;
48
+ }
49
+
50
+ /** "1 waiting for you, 2 running", or "" while nothing waits, runs or failed. */
51
+ export function groupActivityText(summary) {
45
52
  const parts = [];
46
- if (summary.waiting > 0) parts.push(`${summary.waiting} waiting for you`);
47
- if (summary.running > 0) parts.push(`${summary.running} running`);
48
- if (summary.error > 0) parts.push(`${summary.error} failed`);
49
- if (!parts.length) return `${count} session${count === 1 ? '' : 's'}`;
50
- return `${count} · ${parts.join(', ')}`;
53
+ if (Number(summary?.waiting) > 0) parts.push(`${summary.waiting} waiting for you`);
54
+ if (Number(summary?.running) > 0) parts.push(`${summary.running} running`);
55
+ if (Number(summary?.error) > 0) parts.push(`${summary.error} failed`);
56
+ return parts.join(', ');
57
+ }
58
+
59
+ /** "1 session", "4 sessions". */
60
+ export function groupSessionCountText(count) {
61
+ const n = Number(count) || 0;
62
+ return `${n} session${n === 1 ? '' : 's'}`;
51
63
  }
52
64
 
53
65
  /** Whether a group has something going on: a member runs or waits. */
@@ -55,14 +67,39 @@ export function groupIsActive(summary) {
55
67
  return Number(summary?.running || 0) > 0 || Number(summary?.waiting || 0) > 0;
56
68
  }
57
69
 
70
+ /**
71
+ * A member's colour in its group's name: 'waiting', 'running', 'ready' (idle
72
+ * with a live worker, the darker green) or 'offline'. A failed member does
73
+ * not colour the name; the pill says so.
74
+ */
75
+ export function memberTone({ memberState = 'idle', visualState = '' } = {}) {
76
+ if (memberState === 'waiting' || memberState === 'running') return memberState;
77
+ return String(visualState || '').trim().toLowerCase() === 'ready' ? 'ready' : 'offline';
78
+ }
79
+
80
+ /**
81
+ * The colour of a group's name from its members' tones: red while one waits
82
+ * for the user, bright green while one works, the darker green while one is
83
+ * ready, else the plain title colour. Returns 'question' | 'working' |
84
+ * 'ready' | 'offline'.
85
+ */
86
+ export function groupTitleTone(tones = []) {
87
+ const list = Array.isArray(tones) ? tones : [];
88
+ if (list.includes('waiting')) return 'question';
89
+ if (list.includes('running')) return 'working';
90
+ if (list.includes('ready')) return 'ready';
91
+ return 'offline';
92
+ }
93
+
58
94
  /**
59
95
  * Open or folded. The user's choice ('open' | 'folded') wins; otherwise a
60
- * group is open while active and folded once everything is finished. The
61
- * group that holds the open conversation, and every group under a title
62
- * filter, is always open.
96
+ * group is open while active and folded once everything is finished. Every
97
+ * group under a title filter is always open. A folded group that holds the
98
+ * open conversation still shows that row (arrangeConversationList's
99
+ * keepVisibleId).
63
100
  */
64
- export function resolveGroupFold({ choice = null, active = false, holdsOpenConversation = false, filterActive = false } = {}) {
65
- if (holdsOpenConversation || filterActive) return true;
101
+ export function resolveGroupFold({ choice = null, active = false, filterActive = false } = {}) {
102
+ if (filterActive) return true;
66
103
  if (choice === 'open') return true;
67
104
  if (choice === 'folded') return false;
68
105
  return active === true;
@@ -6,7 +6,23 @@
6
6
  // marker is all there is), and neither has one a person started.
7
7
 
8
8
  import { conversations, currentConvId, IS_SHARED_VIEW } from './store.js';
9
- import { renderConversationHeaderOriginHtml } from './remote-relay-origin-view.mjs';
9
+ import { conversationOriginInfo, renderConversationHeaderOriginHtml } from './remote-relay-origin-view.mjs';
10
+
11
+ /**
12
+ * The ︙ entry that stands in for the line where the meta line is hidden
13
+ * (phone landscape): `{ label: 'Started by an agent in “title”',
14
+ * conversationId }` while the line is a button that opens that conversation,
15
+ * else null.
16
+ */
17
+ export function conversationOriginMenuEntry() {
18
+ if (IS_SHARED_VIEW) return null;
19
+ const id = String(currentConvId || '').trim();
20
+ const conversation = id ? (conversations[id] || null) : null;
21
+ if (!conversation) return null;
22
+ const info = conversationOriginInfo(conversation, { conversations, linkable: true });
23
+ if (!info || info.kind !== 'local' || !info.linkable || !info.conversationId) return null;
24
+ return { label: info.label, conversationId: info.conversationId };
25
+ }
10
26
 
11
27
  let renderedHtml = '';
12
28