@oar-sh/oar 0.9.9 → 0.9.11
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.
- package/CHANGELOG.md +561 -1
- package/README.md +162 -909
- package/bin/bat/oar.bat +46 -46
- package/package.json +14 -14
- package/server/README.md +369 -13
- package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
- package/server/claude-worker/claude-session-process.mjs +121 -9
- package/server/claude-worker/claude-session-worker.mjs +9 -1
- package/server/claude-worker/claude-turn-publisher.mjs +3 -0
- package/server/claude-worker/sdk-message-normalizer.mjs +31 -4
- package/server/claude-worker/subagent-timeline-recorder.mjs +572 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +79 -12
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +6 -0
- package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
- package/server/db-schema.mjs +95 -2
- package/server/grok-worker/acp-client.mjs +2 -1
- package/server/grok-worker/acp-host-services.mjs +4 -3
- package/server/migrations/0009-subagent-timeline.mjs +135 -0
- package/server/migrations/0010-subagent-timeline-pruning.mjs +89 -0
- package/server/migrations/0011-relay-event-subscriptions.mjs +125 -0
- package/server/migrations/0012-cross-relay-event-subscriptions.mjs +116 -0
- package/server/migrations/0013-previews.mjs +130 -0
- package/server/migrations/0014-conversation-groups.mjs +89 -0
- package/server/migrations/0015-conversation-group-position.mjs +76 -0
- package/server/migrations/0016-share-access-log.mjs +110 -0
- package/server/public/app/about-ui.js +73 -0
- package/server/public/app/about-view.mjs +234 -0
- package/server/public/app/action-confirmations.js +31 -14
- package/server/public/app/agent-exchange-fold-view.mjs +187 -0
- package/server/public/app/api-client.js +312 -16
- package/server/public/app/archive-confirm.mjs +28 -0
- package/server/public/app/ask-user-view.js +17 -12
- package/server/public/app/attachments-view.js +516 -6
- package/server/public/app/background-tasks-view.mjs +18 -26
- package/server/public/app/bootstrap.js +336 -59
- package/server/public/app/chat-actions-menu-state.mjs +126 -0
- package/server/public/app/claude-cloud-conversation-ui.js +10 -22
- package/server/public/app/composer-control-state.mjs +16 -0
- package/server/public/app/composer-keyboard.mjs +54 -0
- package/server/public/app/context-menu.mjs +90 -6
- package/server/public/app/conversation-group-drag.mjs +191 -0
- package/server/public/app/conversation-group-order.mjs +265 -0
- package/server/public/app/conversation-group-state.mjs +100 -0
- package/server/public/app/conversation-list-drag.mjs +449 -0
- package/server/public/app/conversation-turn-state.mjs +81 -0
- package/server/public/app/conversation-view.js +436 -117
- package/server/public/app/cwd-picker.js +1 -1
- package/server/public/app/explorer-edit-state.mjs +68 -0
- package/server/public/app/hidden-messages-view.mjs +312 -0
- package/server/public/app/host-suspend-ui.js +13 -4
- package/server/public/app/journal-view.js +930 -45
- package/server/public/app/no-conversation-state.mjs +58 -0
- package/server/public/app/preview-cards.mjs +117 -31
- package/server/public/app/preview-command.mjs +10 -1
- package/server/public/app/provider-readiness.mjs +120 -0
- package/server/public/app/relay-board-view.js +19 -3
- package/server/public/app/relay-event-subscriptions-ui.js +184 -0
- package/server/public/app/relay-event-view.mjs +234 -0
- package/server/public/app/relay-restart-error.mjs +16 -0
- package/server/public/app/settings-modal.js +93 -9
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/share-format.mjs +49 -0
- package/server/public/app/share-preview-marks.mjs +167 -0
- package/server/public/app/share-state.js +109 -0
- package/server/public/app/shared-conversations-settings.js +480 -0
- package/server/public/app/socket-handlers.js +137 -40
- package/server/public/app/store.js +115 -32
- package/server/public/app/subagent-row.mjs +138 -0
- package/server/public/app/subagent-timeline-view.mjs +186 -0
- package/server/public/app/subagent-viewer-watch.mjs +50 -0
- package/server/public/app/subagent-viewer.mjs +558 -0
- package/server/public/app/task-row-format.mjs +29 -0
- package/server/public/app/transcript-jump-bottom-view.mjs +464 -0
- package/server/public/app/transcript-jump-bottom.mjs +210 -0
- package/server/public/app/transcript-reload-freshness.mjs +25 -0
- package/server/public/app/transcript-viewport-anchor.mjs +140 -0
- package/server/public/app/update-ui.js +1 -1
- package/server/public/app/viewer-history.mjs +5 -3
- package/server/public/index.html +774 -12
- package/server/relay-tools.md +2 -1
- package/server/relay.mjs +2 -1
- package/server/repositories/conversation-group-repository.mjs +444 -0
- package/server/repositories/message-repository.mjs +30 -3
- package/server/repositories/question-repository.mjs +9 -3
- package/server/repositories/remote-relay-repository.mjs +6 -2
- package/server/repositories/session-repository.mjs +12 -37
- package/server/repositories/share-repository.mjs +322 -0
- package/server/repositories/share-visibility-statements.mjs +10 -0
- package/server/repositories/subagent-timeline-statements.mjs +170 -0
- package/server/routes/about-routes.mjs +15 -0
- package/server/routes/ask-user-routes.mjs +3 -0
- package/server/routes/cache-routes.mjs +4 -0
- package/server/routes/conversation-group-routes.mjs +171 -0
- package/server/routes/messages-routes.mjs +698 -114
- package/server/routes/preview-routes.mjs +9 -1
- package/server/routes/relay-event-routes.mjs +157 -0
- package/server/routes/remote-relay-routes.mjs +15 -0
- package/server/routes/sessions-routes.mjs +843 -193
- package/server/server-runtime.mjs +559 -160
- package/server/services/about-service.mjs +461 -0
- package/server/services/background-task-store.mjs +128 -0
- package/server/services/background-work-view.mjs +228 -0
- package/server/services/cache-rebuild-service.mjs +30 -0
- package/server/services/delete-archive-service.mjs +4 -1
- package/server/services/detached-worker-watch-service.mjs +150 -0
- package/server/services/explorer-entry-name.mjs +26 -0
- package/server/services/explorer-mutations.mjs +122 -0
- package/server/services/hidden-message-list.mjs +21 -0
- package/server/services/host-suspend-command.mjs +2 -1
- package/server/services/message-pin-list.mjs +1 -1
- package/server/services/preview-health-probe.mjs +8 -1
- package/server/services/preview-proxy-server.mjs +49 -27
- package/server/services/preview-registry-service.mjs +676 -26
- package/server/services/preview-static-handler.mjs +40 -7
- package/server/services/preview-store.mjs +274 -0
- package/server/services/push-dispatch-service.mjs +18 -0
- package/server/services/rate-limit-service.mjs +12 -1
- package/server/services/relay-auth.mjs +183 -0
- package/server/services/relay-cli-launcher-service.mjs +3 -1
- package/server/services/relay-event-link-service.mjs +289 -0
- package/server/services/relay-event-service.mjs +1952 -0
- package/server/services/relay-shutdown-gate.mjs +31 -0
- package/server/services/remote-relay-caller-context.mjs +52 -7
- package/server/services/remote-relay-dispatcher.mjs +551 -7
- package/server/services/remote-relay-registry-service.mjs +11 -1
- package/server/services/request-boundary.mjs +183 -0
- package/server/services/session-activity-tracker.mjs +695 -0
- package/server/services/session-history-refresh-service.mjs +7 -0
- package/server/services/session-worker-launch-service.mjs +190 -6
- package/server/services/session-worker-process-service.mjs +10 -6
- package/server/services/share-access-log-service.mjs +123 -0
- package/server/services/share-path-redaction.mjs +424 -0
- package/server/services/stale-attempt-log-limiter.mjs +96 -0
- package/server/services/stale-recovery-clock-guard.mjs +57 -0
- package/server/services/stale-subagent-run-sweep.mjs +109 -0
- package/server/services/status-event-service.mjs +7 -3
- package/server/services/subagent-record-retention.mjs +164 -0
- package/server/services/subagent-timeline-service.mjs +328 -0
- package/server/services/subagent-viewer-rooms.mjs +119 -0
- package/server/services/tunnel-worker-path-guard.mjs +24 -41
- package/server/services/upgrade-socket-guard.mjs +16 -0
- package/server/services/windows-autostart-service.mjs +6 -1
- package/server/services/windows-boot-autostart-service.mjs +207 -23
- package/shared/preview-tool-core.mjs +15 -5
- package/shared/relay-events.mjs +371 -0
- package/shared/remote-relay-contract.mjs +160 -6
- package/shared/share-path-privacy.mjs +30 -0
- package/shared/subagent-record-retention.mjs +31 -0
- package/shared/subagent-timeline.mjs +79 -0
- package/shared/windows-system-tools.mjs +106 -0
- package/shared/worker-runtime/background-task-digest.mjs +30 -0
- package/shared/worker-runtime/heartbeat.mjs +24 -0
- package/shared/worker-runtime/kept-posts.mjs +83 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +47 -15
|
@@ -94,6 +94,135 @@ export function restoreTranscriptAnchor(el, anchor) {
|
|
|
94
94
|
return Number(el.scrollTop || 0) !== before;
|
|
95
95
|
}
|
|
96
96
|
|
|
97
|
+
/** The blocks of a rendered message a reader reads one at a time. */
|
|
98
|
+
export const READING_BLOCK_SELECTOR = 'p, li, pre, h1, h2, h3, h4, h5, h6, tr';
|
|
99
|
+
|
|
100
|
+
// The live bubble of a running turn: a row under its prompt, with the
|
|
101
|
+
// prompt's message id.
|
|
102
|
+
const LIVE_ROW_ID = 'thinking-indicator';
|
|
103
|
+
// How many rows after the prompt the reply is looked for in.
|
|
104
|
+
const REPLY_SEARCH_ROWS = 3;
|
|
105
|
+
|
|
106
|
+
// A block's text without its controls: a finished reply's code block holds
|
|
107
|
+
// its Copy button, the same block of the streamed reply does not.
|
|
108
|
+
function blockText(block) {
|
|
109
|
+
if (typeof block.querySelector !== 'function' || !block.querySelector('button')) {
|
|
110
|
+
return String(block.textContent || '');
|
|
111
|
+
}
|
|
112
|
+
const copy = block.cloneNode(true);
|
|
113
|
+
copy.querySelectorAll('button').forEach((node) => node.remove());
|
|
114
|
+
return String(copy.textContent || '');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function sameBlocks(scope, tag, text) {
|
|
118
|
+
return Array.from(scope.querySelectorAll(tag)).filter((node) => blockText(node) === text);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// A block the reader can see. Inside a closed fold (a reply's thoughts, an
|
|
122
|
+
// event row) it cannot be read, yet the browser still gives it a box there:
|
|
123
|
+
// the fold's content is skipped, not removed from the layout.
|
|
124
|
+
function isReadable(block, rect) {
|
|
125
|
+
if (!(rect.bottom - rect.top > 0)) return false;
|
|
126
|
+
if (typeof block.checkVisibility === 'function') return block.checkVisibility();
|
|
127
|
+
return typeof block.closest !== 'function' || !block.closest('details:not([open]) > :not(summary)');
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The block of text the reader is at: the first one that starts inside the
|
|
132
|
+
* viewport, else the one that reaches into it from above (a code block
|
|
133
|
+
* taller than the screen). Finer than the row anchor above, for the times
|
|
134
|
+
* the rows themselves are rebuilt: a reload of the conversation, or the reply
|
|
135
|
+
* that takes the live bubble's place. There the same text comes back in a
|
|
136
|
+
* row whose other parts (folded thoughts, tool lines, the usage line) have
|
|
137
|
+
* another height, and a row or a pixel anchor moves the text by that much.
|
|
138
|
+
*
|
|
139
|
+
* `scope` limits the search to one element (the live bubble's stream);
|
|
140
|
+
* without it the rows that cross the viewport are searched. Returns null
|
|
141
|
+
* when no block is there (the reader looks at an image, or at a card).
|
|
142
|
+
*/
|
|
143
|
+
export function captureReadingBlock(el, { scope = null } = {}) {
|
|
144
|
+
if (!el) return null;
|
|
145
|
+
const view = el.getBoundingClientRect();
|
|
146
|
+
const scopes = scope ? [scope] : transcriptRows(el).filter((row) => {
|
|
147
|
+
const rect = row.getBoundingClientRect();
|
|
148
|
+
return rect.bottom > view.top && rect.top < view.bottom;
|
|
149
|
+
});
|
|
150
|
+
let found = null;
|
|
151
|
+
let foundScope = null;
|
|
152
|
+
for (const candidate of scopes) {
|
|
153
|
+
for (const block of candidate.querySelectorAll(READING_BLOCK_SELECTOR)) {
|
|
154
|
+
const rect = block.getBoundingClientRect();
|
|
155
|
+
if (!isReadable(block, rect) || rect.bottom <= view.top) continue;
|
|
156
|
+
if (rect.top >= view.bottom) break;
|
|
157
|
+
found = { block, rect };
|
|
158
|
+
foundScope = candidate;
|
|
159
|
+
// The first block that starts in view ends the search; one that only
|
|
160
|
+
// reaches into it is kept until a later one does.
|
|
161
|
+
if (rect.top >= view.top) break;
|
|
162
|
+
}
|
|
163
|
+
if (found && found.rect.top >= view.top) break;
|
|
164
|
+
}
|
|
165
|
+
if (!found) return null;
|
|
166
|
+
const { block, rect } = found;
|
|
167
|
+
const tag = String(block.tagName || '').toLowerCase();
|
|
168
|
+
const text = blockText(block);
|
|
169
|
+
const height = Math.max(0, rect.bottom - rect.top);
|
|
170
|
+
const offset = rect.top - view.top;
|
|
171
|
+
return {
|
|
172
|
+
messageId: String(foundScope?.dataset?.messageId || '').trim() || null,
|
|
173
|
+
// Read in the live bubble: the reply that replaces it is another row.
|
|
174
|
+
live: foundScope?.id === LIVE_ROW_ID,
|
|
175
|
+
tag,
|
|
176
|
+
text,
|
|
177
|
+
// Which of the row's blocks with this very text (a repeated line).
|
|
178
|
+
occurrence: Math.max(0, sameBlocks(foundScope, tag, text).indexOf(block)),
|
|
179
|
+
offset,
|
|
180
|
+
// Scaled to the block's height when its top is scrolled past, so the
|
|
181
|
+
// same share of it stays hidden if it comes back with another height.
|
|
182
|
+
offsetFraction: offset < 0 && height > 0 ? offset / height : null,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// The rows the captured block can be in now. A block read in the live bubble
|
|
187
|
+
// is in that bubble while the turn runs, and in the reply once it has ended:
|
|
188
|
+
// one of the rows that follow the prompt.
|
|
189
|
+
function readingBlockHosts(el, anchor) {
|
|
190
|
+
const messageId = String(anchor.messageId || '').trim();
|
|
191
|
+
if (!messageId) return [];
|
|
192
|
+
const rows = transcriptRows(el);
|
|
193
|
+
const matches = (row) => String(row.dataset?.messageId || '').trim() === messageId;
|
|
194
|
+
if (!anchor.live) return rows.filter((row) => matches(row) && row.id !== LIVE_ROW_ID).slice(0, 1);
|
|
195
|
+
const live = rows.find((row) => matches(row) && row.id === LIVE_ROW_ID);
|
|
196
|
+
if (live) return [live];
|
|
197
|
+
const prompt = rows.findIndex(matches);
|
|
198
|
+
return prompt < 0 ? [] : rows.slice(prompt + 1, prompt + 1 + REPLY_SEARCH_ROWS);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Scrolls so the captured block sits where it was. It is looked for in
|
|
203
|
+
* `scope` when given (the reply that replaced the live bubble), else in the
|
|
204
|
+
* row it was captured in. Returns false when it is not there; the caller
|
|
205
|
+
* falls back to what it did before.
|
|
206
|
+
*/
|
|
207
|
+
export function restoreReadingBlock(el, anchor, { scope = null } = {}) {
|
|
208
|
+
if (!el || !anchor?.tag) return false;
|
|
209
|
+
let block = null;
|
|
210
|
+
for (const host of (scope ? [scope] : readingBlockHosts(el, anchor))) {
|
|
211
|
+
const matches = sameBlocks(host, anchor.tag, anchor.text).filter((node) => isReadable(node, node.getBoundingClientRect()));
|
|
212
|
+
block = matches[anchor.occurrence] || matches[0] || null;
|
|
213
|
+
if (block) break;
|
|
214
|
+
}
|
|
215
|
+
if (!block) return false;
|
|
216
|
+
const view = el.getBoundingClientRect();
|
|
217
|
+
const rect = block.getBoundingClientRect();
|
|
218
|
+
const target = anchor.offsetFraction === null || anchor.offsetFraction === undefined
|
|
219
|
+
? Number(anchor.offset || 0)
|
|
220
|
+
: Number(anchor.offsetFraction) * Math.max(0, rect.bottom - rect.top);
|
|
221
|
+
const delta = rect.top - view.top - target;
|
|
222
|
+
if (Math.abs(delta) > 0.5) el.scrollTop = Number(el.scrollTop || 0) + delta;
|
|
223
|
+
return true;
|
|
224
|
+
}
|
|
225
|
+
|
|
97
226
|
const KEYBOARD_INPUT_TYPES = new Set(['', 'text', 'search', 'email', 'url', 'tel', 'number', 'password']);
|
|
98
227
|
|
|
99
228
|
/**
|
|
@@ -201,8 +330,19 @@ export function createTranscriptResizeKeeper({
|
|
|
201
330
|
return apply(el);
|
|
202
331
|
}
|
|
203
332
|
|
|
333
|
+
// Content landed under a reader who followed the end and was not followed
|
|
334
|
+
// (no scroll event tells): the position is what it is now, not "the end".
|
|
335
|
+
function recordEndLeft() {
|
|
336
|
+
if (settleTimer !== null) return false;
|
|
337
|
+
const el = element();
|
|
338
|
+
const next = el ? capture(el) : null;
|
|
339
|
+
if (next) committed = next;
|
|
340
|
+
return Boolean(next);
|
|
341
|
+
}
|
|
342
|
+
|
|
204
343
|
return {
|
|
205
344
|
recordScroll,
|
|
345
|
+
recordEndLeft,
|
|
206
346
|
handleResize,
|
|
207
347
|
isSettling: () => settleTimer !== null,
|
|
208
348
|
committedAnchor: () => committed,
|
|
@@ -100,7 +100,7 @@ export function renderUpdateSection() {
|
|
|
100
100
|
status.textContent = install.state === 'running'
|
|
101
101
|
? `Updating to ${install.targetVersion}…`
|
|
102
102
|
: install.state === 'success'
|
|
103
|
-
? 'npm finished — the relay restarts
|
|
103
|
+
? 'npm finished — the relay restarts once the running turns finish.'
|
|
104
104
|
: `Update failed: ${install.error || 'unknown error'}`;
|
|
105
105
|
}
|
|
106
106
|
const logPre = byId('update-install-log-pre');
|
|
@@ -9,21 +9,23 @@
|
|
|
9
9
|
export const VIEWER_HISTORY_MARK = 'oarFileViewer';
|
|
10
10
|
const OWN_POP_WINDOW_MS = 1500;
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
// `mark` names the viewer's entry: two viewers (the file viewer and the
|
|
13
|
+
// sub-agent viewer) must never take each other's entry for their own.
|
|
14
|
+
export function createViewerHistory({ history, onBack, now = () => Date.now(), mark = VIEWER_HISTORY_MARK } = {}) {
|
|
13
15
|
const state = { pushed: false, ownPopAt: 0 };
|
|
14
16
|
return {
|
|
15
17
|
get pushed() { return state.pushed; },
|
|
16
18
|
/** The viewer came up: one entry, however many files it then shows. */
|
|
17
19
|
opened() {
|
|
18
20
|
if (state.pushed) return;
|
|
19
|
-
history.pushState({ [
|
|
21
|
+
history.pushState({ [mark]: true }, '');
|
|
20
22
|
state.pushed = true;
|
|
21
23
|
},
|
|
22
24
|
/** The viewer went away by itself: take its entry back out of history. */
|
|
23
25
|
closed() {
|
|
24
26
|
if (!state.pushed) return;
|
|
25
27
|
state.pushed = false;
|
|
26
|
-
if (history.state?.[
|
|
28
|
+
if (history.state?.[mark]) {
|
|
27
29
|
state.ownPopAt = now();
|
|
28
30
|
history.back();
|
|
29
31
|
}
|