@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
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import net from 'net';
|
|
4
4
|
|
|
5
|
-
// Liveness for preview cards. Previews never
|
|
5
|
+
// Liveness for preview cards. Previews never expire, so the card has to say
|
|
6
6
|
// whether the dev server behind it is still up — a TCP connect is enough and,
|
|
7
7
|
// unlike an HTTP request, leaves nothing in the app's request log.
|
|
8
8
|
|
|
@@ -55,6 +55,10 @@ export function createPreviewHealthProbe({
|
|
|
55
55
|
// A slow sweep must not overlap itself; with the registry capped at 8
|
|
56
56
|
// previews and a 250ms connect budget, one pass is bounded at ~2s anyway.
|
|
57
57
|
if (sweeping) return;
|
|
58
|
+
// Store writes that failed (a locked database) are retried on this beat,
|
|
59
|
+
// so the table catches up with memory without a timer of its own, and
|
|
60
|
+
// rows a restore could not judge get their check again.
|
|
61
|
+
try { registry.maintain?.(); } catch {}
|
|
58
62
|
const previews = registry.list().filter((entry) => entry.mode !== 'static');
|
|
59
63
|
if (previews.length === 0) return;
|
|
60
64
|
sweeping = true;
|
|
@@ -70,6 +74,9 @@ export function createPreviewHealthProbe({
|
|
|
70
74
|
timer = setIntervalImpl(() => { sweep().catch(() => {}); }, intervalMs);
|
|
71
75
|
// An idle relay should not be held awake by a probe that has nothing to do.
|
|
72
76
|
if (typeof timer?.unref === 'function') timer.unref();
|
|
77
|
+
// One pass right away: port previews restored at boot start without a
|
|
78
|
+
// health state, and their cards should not wait a full interval for one.
|
|
79
|
+
sweep().catch(() => {});
|
|
73
80
|
}
|
|
74
81
|
|
|
75
82
|
function stop() {
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import http from 'http';
|
|
4
4
|
|
|
5
|
-
import { parsePreviewPath } from './preview-registry-service.mjs';
|
|
5
|
+
import { parsePreviewPath, previewLogId } from './preview-registry-service.mjs';
|
|
6
6
|
import { serveStaticPreview } from './preview-static-handler.mjs';
|
|
7
7
|
|
|
8
8
|
// The preview listener. A bare http.Server on its own loopback port whose
|
|
@@ -270,12 +270,16 @@ export function createPreviewProxyServer({
|
|
|
270
270
|
socket.once('error', forget);
|
|
271
271
|
}
|
|
272
272
|
|
|
273
|
-
|
|
273
|
+
// The token is the capability and the request URL carries it, so the line
|
|
274
|
+
// names the preview by a hash and logs only the upstream path, without query.
|
|
275
|
+
function logHit(entry, req, upstreamPath) {
|
|
274
276
|
const now = Date.now();
|
|
275
277
|
const last = lastHitLogAt.get(entry.token) || 0;
|
|
276
278
|
if (now - last < 60_000) return;
|
|
277
279
|
lastHitLogAt.set(entry.token, now);
|
|
278
|
-
|
|
280
|
+
const target = entry.mode === 'static' ? 'static' : `:${entry.targetPort}`;
|
|
281
|
+
const pathOnly = String(upstreamPath || '/').split('?')[0];
|
|
282
|
+
logger?.log?.(`[preview] ${previewLogId(entry.token)} → ${target} ${req.method} ${pathOnly} from ${clientIp(req)}`);
|
|
279
283
|
}
|
|
280
284
|
|
|
281
285
|
function resolveRequest(req) {
|
|
@@ -309,7 +313,7 @@ export function createPreviewProxyServer({
|
|
|
309
313
|
|
|
310
314
|
const { entry, upstreamPath } = resolved;
|
|
311
315
|
registry.recordHit(entry.token);
|
|
312
|
-
logHit(entry, req);
|
|
316
|
+
logHit(entry, req, upstreamPath);
|
|
313
317
|
|
|
314
318
|
// Static previews are answered in-process — no upstream, no forwarding.
|
|
315
319
|
if (entry.mode === 'static') {
|
|
@@ -399,13 +403,33 @@ export function createPreviewProxyServer({
|
|
|
399
403
|
|
|
400
404
|
// WebSocket upgrades (Vite HMR, an app's own socket) ride the same prefix.
|
|
401
405
|
function handleUpgrade(req, socket, head) {
|
|
406
|
+
// Node removes its own error listener from a socket the moment it emits
|
|
407
|
+
// 'upgrade', and the upstream socket handed to 'upgrade' comes bare too. A
|
|
408
|
+
// browser that drops the connection while the dev server is still
|
|
409
|
+
// answering would otherwise raise an uncaught ECONNRESET and end the
|
|
410
|
+
// whole relay. Whatever fails on either side takes both down.
|
|
411
|
+
let upstreamReq = null;
|
|
412
|
+
let upstreamSocket = null;
|
|
413
|
+
const teardown = () => {
|
|
414
|
+
try { socket.destroy(); } catch {}
|
|
415
|
+
try { upstreamSocket?.destroy(); } catch {}
|
|
416
|
+
try { upstreamReq?.destroy(); } catch {}
|
|
417
|
+
};
|
|
418
|
+
socket.on('error', teardown);
|
|
419
|
+
// A client that leaves before the dev server answers abandons the
|
|
420
|
+
// handshake. Once both sides are piped, an orderly close travels through
|
|
421
|
+
// the pipes, so the pending writes still reach the other side.
|
|
422
|
+
socket.once('close', teardown);
|
|
423
|
+
const reply = (text) => {
|
|
424
|
+
if (socket.destroyed) return;
|
|
425
|
+
try { socket.write(text); } catch {}
|
|
426
|
+
};
|
|
427
|
+
|
|
402
428
|
const resolved = resolveRequest(req);
|
|
403
429
|
// Static previews have nothing to upgrade to; refuse like an unknown token.
|
|
404
430
|
if (resolved.kind !== 'proxy' || resolved.entry?.mode === 'static') {
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
} catch {}
|
|
408
|
-
try { socket.destroy(); } catch {}
|
|
431
|
+
reply('HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n');
|
|
432
|
+
teardown();
|
|
409
433
|
return;
|
|
410
434
|
}
|
|
411
435
|
|
|
@@ -426,7 +450,7 @@ export function createPreviewProxyServer({
|
|
|
426
450
|
headers.connection = 'Upgrade';
|
|
427
451
|
headers.upgrade = String(req.headers.upgrade || 'websocket');
|
|
428
452
|
|
|
429
|
-
|
|
453
|
+
upstreamReq = httpImpl.request({
|
|
430
454
|
host: entry.targetHost,
|
|
431
455
|
port: entry.targetPort,
|
|
432
456
|
method: req.method,
|
|
@@ -436,25 +460,26 @@ export function createPreviewProxyServer({
|
|
|
436
460
|
});
|
|
437
461
|
guardConnectTimeout(upstreamReq, connectTimeoutMs, () => {});
|
|
438
462
|
|
|
439
|
-
upstreamReq.on('upgrade', (upstreamRes,
|
|
463
|
+
upstreamReq.on('upgrade', (upstreamRes, answeredSocket, upstreamHead) => {
|
|
464
|
+
upstreamSocket = answeredSocket;
|
|
465
|
+
upstreamSocket.on('error', teardown);
|
|
466
|
+
// The client left while the dev server was answering.
|
|
467
|
+
if (socket.destroyed) {
|
|
468
|
+
teardown();
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
440
471
|
const lines = [`HTTP/1.1 ${upstreamRes.statusCode} ${upstreamRes.statusMessage}`];
|
|
441
472
|
for (const [name, value] of Object.entries(upstreamRes.headers)) {
|
|
442
473
|
for (const entryValue of Array.isArray(value) ? value : [value]) {
|
|
443
474
|
lines.push(`${name}: ${entryValue}`);
|
|
444
475
|
}
|
|
445
476
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
} catch {
|
|
449
|
-
try { upstreamSocket.destroy(); } catch {}
|
|
450
|
-
return;
|
|
451
|
-
}
|
|
452
|
-
if (upstreamHead?.length) socket.write(upstreamHead);
|
|
477
|
+
reply(`${lines.join('\r\n')}\r\n\r\n`);
|
|
478
|
+
if (upstreamHead?.length) reply(upstreamHead);
|
|
453
479
|
if (head?.length) upstreamSocket.write(head);
|
|
480
|
+
socket.removeListener('close', teardown);
|
|
454
481
|
trackUpgradedSocket(socket);
|
|
455
482
|
trackUpgradedSocket(upstreamSocket);
|
|
456
|
-
upstreamSocket.on('error', () => { try { socket.destroy(); } catch {} });
|
|
457
|
-
socket.on('error', () => { try { upstreamSocket.destroy(); } catch {} });
|
|
458
483
|
upstreamSocket.pipe(socket);
|
|
459
484
|
socket.pipe(upstreamSocket);
|
|
460
485
|
});
|
|
@@ -462,18 +487,15 @@ export function createPreviewProxyServer({
|
|
|
462
487
|
// A dev server that answers an upgrade with a plain response (404 on an
|
|
463
488
|
// unknown HMR path) must not leave the client hanging.
|
|
464
489
|
upstreamReq.on('response', (upstreamRes) => {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
} catch {}
|
|
490
|
+
reply(`HTTP/1.1 ${upstreamRes.statusCode} ${upstreamRes.statusMessage}\r\nConnection: close\r\n\r\n`);
|
|
491
|
+
upstreamRes.on('error', () => {});
|
|
468
492
|
upstreamRes.resume();
|
|
469
|
-
|
|
493
|
+
teardown();
|
|
470
494
|
});
|
|
471
495
|
|
|
472
496
|
upstreamReq.on('error', () => {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
} catch {}
|
|
476
|
-
try { socket.destroy(); } catch {}
|
|
497
|
+
reply('HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n');
|
|
498
|
+
teardown();
|
|
477
499
|
});
|
|
478
500
|
|
|
479
501
|
upstreamReq.end();
|