@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.
Files changed (154) hide show
  1. package/CHANGELOG.md +561 -1
  2. package/README.md +162 -909
  3. package/bin/bat/oar.bat +46 -46
  4. package/package.json +14 -14
  5. package/server/README.md +369 -13
  6. package/server/claude-worker/claude-remote-relay-tool.mjs +10 -0
  7. package/server/claude-worker/claude-session-process.mjs +121 -9
  8. package/server/claude-worker/claude-session-worker.mjs +9 -1
  9. package/server/claude-worker/claude-turn-publisher.mjs +3 -0
  10. package/server/claude-worker/sdk-message-normalizer.mjs +31 -4
  11. package/server/claude-worker/subagent-timeline-recorder.mjs +572 -0
  12. package/server/copilot-worker/copilot-sdk-session-process.mjs +79 -12
  13. package/server/copilot-worker/copilot-sdk-session-worker.mjs +6 -0
  14. package/server/cursor-worker/cursor-sdk-adapter.mjs +56 -2
  15. package/server/db-schema.mjs +95 -2
  16. package/server/grok-worker/acp-client.mjs +2 -1
  17. package/server/grok-worker/acp-host-services.mjs +4 -3
  18. package/server/migrations/0009-subagent-timeline.mjs +135 -0
  19. package/server/migrations/0010-subagent-timeline-pruning.mjs +89 -0
  20. package/server/migrations/0011-relay-event-subscriptions.mjs +125 -0
  21. package/server/migrations/0012-cross-relay-event-subscriptions.mjs +116 -0
  22. package/server/migrations/0013-previews.mjs +130 -0
  23. package/server/migrations/0014-conversation-groups.mjs +89 -0
  24. package/server/migrations/0015-conversation-group-position.mjs +76 -0
  25. package/server/migrations/0016-share-access-log.mjs +110 -0
  26. package/server/public/app/about-ui.js +73 -0
  27. package/server/public/app/about-view.mjs +234 -0
  28. package/server/public/app/action-confirmations.js +31 -14
  29. package/server/public/app/agent-exchange-fold-view.mjs +187 -0
  30. package/server/public/app/api-client.js +312 -16
  31. package/server/public/app/archive-confirm.mjs +28 -0
  32. package/server/public/app/ask-user-view.js +17 -12
  33. package/server/public/app/attachments-view.js +516 -6
  34. package/server/public/app/background-tasks-view.mjs +18 -26
  35. package/server/public/app/bootstrap.js +336 -59
  36. package/server/public/app/chat-actions-menu-state.mjs +126 -0
  37. package/server/public/app/claude-cloud-conversation-ui.js +10 -22
  38. package/server/public/app/composer-control-state.mjs +16 -0
  39. package/server/public/app/composer-keyboard.mjs +54 -0
  40. package/server/public/app/context-menu.mjs +90 -6
  41. package/server/public/app/conversation-group-drag.mjs +191 -0
  42. package/server/public/app/conversation-group-order.mjs +265 -0
  43. package/server/public/app/conversation-group-state.mjs +100 -0
  44. package/server/public/app/conversation-list-drag.mjs +449 -0
  45. package/server/public/app/conversation-turn-state.mjs +81 -0
  46. package/server/public/app/conversation-view.js +436 -117
  47. package/server/public/app/cwd-picker.js +1 -1
  48. package/server/public/app/explorer-edit-state.mjs +68 -0
  49. package/server/public/app/hidden-messages-view.mjs +312 -0
  50. package/server/public/app/host-suspend-ui.js +13 -4
  51. package/server/public/app/journal-view.js +930 -45
  52. package/server/public/app/no-conversation-state.mjs +58 -0
  53. package/server/public/app/preview-cards.mjs +117 -31
  54. package/server/public/app/preview-command.mjs +10 -1
  55. package/server/public/app/provider-readiness.mjs +120 -0
  56. package/server/public/app/relay-board-view.js +19 -3
  57. package/server/public/app/relay-event-subscriptions-ui.js +184 -0
  58. package/server/public/app/relay-event-view.mjs +234 -0
  59. package/server/public/app/relay-restart-error.mjs +16 -0
  60. package/server/public/app/settings-modal.js +93 -9
  61. package/server/public/app/settings-tabs.js +1 -1
  62. package/server/public/app/share-format.mjs +49 -0
  63. package/server/public/app/share-preview-marks.mjs +167 -0
  64. package/server/public/app/share-state.js +109 -0
  65. package/server/public/app/shared-conversations-settings.js +480 -0
  66. package/server/public/app/socket-handlers.js +137 -40
  67. package/server/public/app/store.js +115 -32
  68. package/server/public/app/subagent-row.mjs +138 -0
  69. package/server/public/app/subagent-timeline-view.mjs +186 -0
  70. package/server/public/app/subagent-viewer-watch.mjs +50 -0
  71. package/server/public/app/subagent-viewer.mjs +558 -0
  72. package/server/public/app/task-row-format.mjs +29 -0
  73. package/server/public/app/transcript-jump-bottom-view.mjs +464 -0
  74. package/server/public/app/transcript-jump-bottom.mjs +210 -0
  75. package/server/public/app/transcript-reload-freshness.mjs +25 -0
  76. package/server/public/app/transcript-viewport-anchor.mjs +140 -0
  77. package/server/public/app/update-ui.js +1 -1
  78. package/server/public/app/viewer-history.mjs +5 -3
  79. package/server/public/index.html +774 -12
  80. package/server/relay-tools.md +2 -1
  81. package/server/relay.mjs +2 -1
  82. package/server/repositories/conversation-group-repository.mjs +444 -0
  83. package/server/repositories/message-repository.mjs +30 -3
  84. package/server/repositories/question-repository.mjs +9 -3
  85. package/server/repositories/remote-relay-repository.mjs +6 -2
  86. package/server/repositories/session-repository.mjs +12 -37
  87. package/server/repositories/share-repository.mjs +322 -0
  88. package/server/repositories/share-visibility-statements.mjs +10 -0
  89. package/server/repositories/subagent-timeline-statements.mjs +170 -0
  90. package/server/routes/about-routes.mjs +15 -0
  91. package/server/routes/ask-user-routes.mjs +3 -0
  92. package/server/routes/cache-routes.mjs +4 -0
  93. package/server/routes/conversation-group-routes.mjs +171 -0
  94. package/server/routes/messages-routes.mjs +698 -114
  95. package/server/routes/preview-routes.mjs +9 -1
  96. package/server/routes/relay-event-routes.mjs +157 -0
  97. package/server/routes/remote-relay-routes.mjs +15 -0
  98. package/server/routes/sessions-routes.mjs +843 -193
  99. package/server/server-runtime.mjs +559 -160
  100. package/server/services/about-service.mjs +461 -0
  101. package/server/services/background-task-store.mjs +128 -0
  102. package/server/services/background-work-view.mjs +228 -0
  103. package/server/services/cache-rebuild-service.mjs +30 -0
  104. package/server/services/delete-archive-service.mjs +4 -1
  105. package/server/services/detached-worker-watch-service.mjs +150 -0
  106. package/server/services/explorer-entry-name.mjs +26 -0
  107. package/server/services/explorer-mutations.mjs +122 -0
  108. package/server/services/hidden-message-list.mjs +21 -0
  109. package/server/services/host-suspend-command.mjs +2 -1
  110. package/server/services/message-pin-list.mjs +1 -1
  111. package/server/services/preview-health-probe.mjs +8 -1
  112. package/server/services/preview-proxy-server.mjs +49 -27
  113. package/server/services/preview-registry-service.mjs +676 -26
  114. package/server/services/preview-static-handler.mjs +40 -7
  115. package/server/services/preview-store.mjs +274 -0
  116. package/server/services/push-dispatch-service.mjs +18 -0
  117. package/server/services/rate-limit-service.mjs +12 -1
  118. package/server/services/relay-auth.mjs +183 -0
  119. package/server/services/relay-cli-launcher-service.mjs +3 -1
  120. package/server/services/relay-event-link-service.mjs +289 -0
  121. package/server/services/relay-event-service.mjs +1952 -0
  122. package/server/services/relay-shutdown-gate.mjs +31 -0
  123. package/server/services/remote-relay-caller-context.mjs +52 -7
  124. package/server/services/remote-relay-dispatcher.mjs +551 -7
  125. package/server/services/remote-relay-registry-service.mjs +11 -1
  126. package/server/services/request-boundary.mjs +183 -0
  127. package/server/services/session-activity-tracker.mjs +695 -0
  128. package/server/services/session-history-refresh-service.mjs +7 -0
  129. package/server/services/session-worker-launch-service.mjs +190 -6
  130. package/server/services/session-worker-process-service.mjs +10 -6
  131. package/server/services/share-access-log-service.mjs +123 -0
  132. package/server/services/share-path-redaction.mjs +424 -0
  133. package/server/services/stale-attempt-log-limiter.mjs +96 -0
  134. package/server/services/stale-recovery-clock-guard.mjs +57 -0
  135. package/server/services/stale-subagent-run-sweep.mjs +109 -0
  136. package/server/services/status-event-service.mjs +7 -3
  137. package/server/services/subagent-record-retention.mjs +164 -0
  138. package/server/services/subagent-timeline-service.mjs +328 -0
  139. package/server/services/subagent-viewer-rooms.mjs +119 -0
  140. package/server/services/tunnel-worker-path-guard.mjs +24 -41
  141. package/server/services/upgrade-socket-guard.mjs +16 -0
  142. package/server/services/windows-autostart-service.mjs +6 -1
  143. package/server/services/windows-boot-autostart-service.mjs +207 -23
  144. package/shared/preview-tool-core.mjs +15 -5
  145. package/shared/relay-events.mjs +371 -0
  146. package/shared/remote-relay-contract.mjs +160 -6
  147. package/shared/share-path-privacy.mjs +30 -0
  148. package/shared/subagent-record-retention.mjs +31 -0
  149. package/shared/subagent-timeline.mjs +79 -0
  150. package/shared/windows-system-tools.mjs +106 -0
  151. package/shared/worker-runtime/background-task-digest.mjs +30 -0
  152. package/shared/worker-runtime/heartbeat.mjs +24 -0
  153. package/shared/worker-runtime/kept-posts.mjs +83 -0
  154. 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 auto-close, so the card has to say
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
- function logHit(entry, req) {
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
- logger?.log?.(`[preview] ${entry.token.slice(0, 8)} → :${entry.targetPort} ${req.method} ${req.url} from ${clientIp(req)}`);
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
- try {
406
- socket.write('HTTP/1.1 404 Not Found\r\nConnection: close\r\n\r\n');
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
- const upstreamReq = httpImpl.request({
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, upstreamSocket, upstreamHead) => {
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
- try {
447
- socket.write(`${lines.join('\r\n')}\r\n\r\n`);
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
- try {
466
- socket.write(`HTTP/1.1 ${upstreamRes.statusCode} ${upstreamRes.statusMessage}\r\nConnection: close\r\n\r\n`);
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
- try { socket.destroy(); } catch {}
493
+ teardown();
470
494
  });
471
495
 
472
496
  upstreamReq.on('error', () => {
473
- try {
474
- socket.write('HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\n\r\n');
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();