@oar-sh/oar 0.9.14 → 0.9.16

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 (59) hide show
  1. package/CHANGELOG.md +179 -0
  2. package/README.md +9 -9
  3. package/bin/oar.js +525 -12
  4. package/package.json +3 -1
  5. package/server/claude-worker/claude-remote-relay-tool.mjs +36 -1
  6. package/server/claude-worker/claude-session-process.mjs +4 -4
  7. package/server/copilot-worker/copilot-sdk-session-process.mjs +2 -2
  8. package/server/db-schema.mjs +52 -0
  9. package/server/migrations/0019-relay-event-subscription-options.mjs +106 -0
  10. package/server/migrations/0020-conversation-may-create-sessions.mjs +67 -0
  11. package/server/migrations/0021-conversation-group-agent-marks.mjs +77 -0
  12. package/server/migrations/0022-relay-schedules.mjs +142 -0
  13. package/server/migrations/0023-relay-asks.mjs +141 -0
  14. package/server/public/app/ask-user-view.js +3 -0
  15. package/server/public/app/bootstrap.js +61 -3
  16. package/server/public/app/context-usage-view.mjs +17 -0
  17. package/server/public/app/conversation-group-order.mjs +35 -0
  18. package/server/public/app/inbox-ui.js +356 -0
  19. package/server/public/app/inbox-view.mjs +370 -0
  20. package/server/public/app/journal-view.js +23 -2
  21. package/server/public/app/relay-event-subscriptions-ui.js +4 -2
  22. package/server/public/app/relay-event-view.mjs +33 -7
  23. package/server/public/app/relay-schedule-view.mjs +119 -0
  24. package/server/public/app/relay-schedules-ui.js +172 -0
  25. package/server/public/app/settings-modal.js +17 -3
  26. package/server/public/app/socket-handlers.js +27 -3
  27. package/server/public/app/store.js +18 -5
  28. package/server/public/index.html +159 -2
  29. package/server/relay-tools.md +18 -3
  30. package/server/repositories/conversation-group-repository.mjs +97 -11
  31. package/server/repositories/remote-relay-repository.mjs +20 -2
  32. package/server/repositories/session-repository.mjs +7 -1
  33. package/server/routes/ask-user-routes.mjs +57 -3
  34. package/server/routes/cache-routes.mjs +6 -0
  35. package/server/routes/conversation-group-routes.mjs +17 -2
  36. package/server/routes/messages-routes.mjs +3 -3
  37. package/server/routes/relay-ask-routes.mjs +387 -0
  38. package/server/routes/relay-event-routes.mjs +3 -1
  39. package/server/routes/relay-schedule-routes.mjs +57 -0
  40. package/server/routes/sessions-routes.mjs +613 -56
  41. package/server/server-runtime.mjs +80 -1
  42. package/server/services/cache-rebuild-service.mjs +19 -2
  43. package/server/services/conversation-preferences-service.mjs +17 -0
  44. package/server/services/oar-cli-helpers.mjs +12 -4
  45. package/server/services/relay-ask-link-service.mjs +174 -0
  46. package/server/services/relay-ask-service.mjs +604 -0
  47. package/server/services/relay-event-service.mjs +589 -73
  48. package/server/services/relay-schedule-service.mjs +596 -0
  49. package/server/services/remote-relay-caller-context.mjs +89 -20
  50. package/server/services/remote-relay-dispatcher.mjs +956 -30
  51. package/server/services/windows-autostart-service.mjs +88 -16
  52. package/server/services/windows-boot-autostart-service.mjs +775 -126
  53. package/shared/relay-asks.mjs +145 -0
  54. package/shared/relay-events.mjs +268 -18
  55. package/shared/relay-schedules.mjs +128 -0
  56. package/shared/remote-relay-contract.mjs +553 -25
  57. package/shared/remote-relay-tool-core.mjs +16 -5
  58. package/.github/extensions/web-relay/README.md +0 -190
  59. package/server/README.md +0 -2334
@@ -191,6 +191,31 @@ export function readLauncherNodePath(script, serverPath) {
191
191
  return null;
192
192
  }
193
193
 
194
+ /**
195
+ * The package folder of another OAR install a launcher starts (its Node line
196
+ * runs a `server\server.js` other than `packageRoot`'s), or null.
197
+ */
198
+ export function otherInstallOf(script, packageRoot, pathImpl = path) {
199
+ const own = pathImpl.resolve(String(packageRoot || '')).toLowerCase();
200
+ for (const line of String(script == null ? '' : script).split(/\r?\n/)) {
201
+ const match = line.trimEnd().match(/^"[^"]+" "([^"]+)[\\/]server[\\/]server\.js"(?: & exit(?: \/b)?)?$/i);
202
+ if (match && pathImpl.resolve(match[1].replaceAll('%%', '%')).toLowerCase() !== own) return match[1].replaceAll('%%', '%');
203
+ }
204
+ return null;
205
+ }
206
+
207
+ /**
208
+ * Where a copy of `target` goes before `--replace` writes over or removes
209
+ * it: `<name>.bak` in `dir`, numbered when one is there already, so no
210
+ * earlier copy is lost.
211
+ */
212
+ export function backupPathFor(target, dir, fsImpl = fs, pathImpl = path) {
213
+ const base = pathImpl.join(dir, `${pathImpl.basename(target)}.bak`);
214
+ let candidate = base;
215
+ for (let index = 2; fsImpl.existsSync(candidate); index += 1) candidate = `${base}-${index}`;
216
+ return candidate;
217
+ }
218
+
194
219
  /**
195
220
  * Same launcher, whatever the line endings, trailing blanks and the length
196
221
  * of the padding. `ignoreCase` for telling whether a launcher is one an
@@ -346,13 +371,6 @@ export function createWindowsAutostartService({
346
371
  };
347
372
  }
348
373
 
349
- function removeLegacyEntry() {
350
- const target = legacyEntryPath();
351
- if (!fsImpl.existsSync(target)) return false;
352
- fsImpl.unlinkSync(target);
353
- return true;
354
- }
355
-
356
374
  const workingDirectory = () => resolveOarRoot(env, platform);
357
375
  const serverPath = () => pathImpl.join(pathImpl.resolve(String(packageRoot || '')), 'server', 'server.js');
358
376
 
@@ -418,7 +436,46 @@ export function createWindowsAutostartService({
418
436
  return changed;
419
437
  }
420
438
 
421
- function setEnabled(enabled) {
439
+ /**
440
+ * Whether `script` is an entry OAR generates or generated for this install
441
+ * and its config (with whatever Node it runs). Anything else, an entry
442
+ * edited by hand or one of another install, is hand-made.
443
+ */
444
+ function isGenerated(script) {
445
+ const entryNodePath = readLauncherNodePath(script, serverPath());
446
+ if (!entryNodePath) return false;
447
+ const stateDirs = resolveRelayStateDirs({ packageRoot, env, platform, fsImpl, pathImpl });
448
+ return launcherScriptsMatch(script, buildScript({ stateDirs, nodePathOverride: entryNodePath }))
449
+ || buildEarlierWindowsAutostartScripts({
450
+ packageRoot, nodePath: entryNodePath, configPath, ...stateDirs, workingDirectory: workingDirectory(), pathImpl,
451
+ }).some((earlier) => launcherScriptsMatch(script, earlier, { ignoreCase: true }));
452
+ }
453
+
454
+ /**
455
+ * The sign-in entries there are, each `{ path, generated, otherInstall }`:
456
+ * `otherInstall` is the package folder of the other OAR install a
457
+ * hand-made entry starts (an entry left from a moved or second install).
458
+ */
459
+ function listEntries() {
460
+ if (!supported) return [];
461
+ return [entryPath(), legacyEntryPath()]
462
+ .filter((target) => fsImpl.existsSync(target))
463
+ .map((target) => {
464
+ const script = String(fsImpl.readFileSync(target, 'utf8'));
465
+ const generated = isGenerated(script);
466
+ return { path: target, generated, otherInstall: generated ? null : otherInstallOf(script, packageRoot, pathImpl) };
467
+ });
468
+ }
469
+
470
+ /**
471
+ * Switches the sign-in entry on or off. Only entries OAR generated are
472
+ * written over or removed: a hand-made one stays byte-identical and is
473
+ * returned in `kept`, and while one is there no second entry is added.
474
+ * `replace` (with `backupDir`, never inside the Startup folder, which
475
+ * would open the copy at sign-in) first moves each hand-made entry there
476
+ * as `<name>.bak`, listed in `backedUp`.
477
+ */
478
+ function setEnabled(enabled, { replace = false, backupDir = '' } = {}) {
422
479
  if (!supported) {
423
480
  throw new Error('Windows autostart is only available on Windows.');
424
481
  }
@@ -426,28 +483,43 @@ export function createWindowsAutostartService({
426
483
  throw new TypeError('Windows autostart enabled state must be a boolean.');
427
484
  }
428
485
 
429
- const targetPath = entryPath();
430
- const existed = fsImpl.existsSync(targetPath);
486
+ const backedUp = [];
487
+ if (replace) {
488
+ if (!String(backupDir || '').trim()) throw new Error('Replacing a hand-made entry needs a folder for its copy.');
489
+ for (const entry of listEntries().filter((listed) => !listed.generated)) {
490
+ fsImpl.mkdirSync(backupDir, { recursive: true });
491
+ const copy = backupPathFor(entry.path, backupDir, fsImpl, pathImpl);
492
+ fsImpl.copyFileSync(entry.path, copy);
493
+ fsImpl.unlinkSync(entry.path);
494
+ backedUp.push({ path: entry.path, backup: copy });
495
+ }
496
+ }
497
+ const entries = listEntries();
498
+ const kept = entries.filter((entry) => !entry.generated).map((entry) => entry.path);
499
+ const remove = (entry) => { fsImpl.unlinkSync(entry.path); return true; };
431
500
  if (!enabled) {
432
- const removedLegacy = removeLegacyEntry();
433
- if (existed) fsImpl.unlinkSync(targetPath);
434
- return { ...getState(), changed: existed || removedLegacy };
501
+ const removed = entries.filter((entry) => entry.generated).map(remove);
502
+ return { ...getState(), changed: removed.length + backedUp.length > 0, kept, backedUp };
435
503
  }
504
+ if (kept.length) return { ...getState(), changed: false, kept, backedUp };
436
505
  // Enabling always converges on the one current filename; if only the
437
506
  // legacy entry existed, the current-name write below reports the change.
438
- removeLegacyEntry();
507
+ entries.filter((entry) => entry.path === legacyEntryPath()).forEach(remove);
439
508
 
509
+ const targetPath = entryPath();
510
+ const existed = fsImpl.existsSync(targetPath);
440
511
  const current = existed ? String(fsImpl.readFileSync(targetPath, 'utf8')) : '';
441
512
  if (existed && launcherScriptsMatch(current, buildScript())) {
442
- return { ...getState(), changed: false };
513
+ return { ...getState(), changed: backedUp.length > 0, kept, backedUp };
443
514
  }
444
515
 
445
516
  writeEntry(targetPath, buildScript({ coverBytes: Buffer.byteLength(current, 'utf8') }));
446
- return { ...getState(), changed: true };
517
+ return { ...getState(), changed: true, kept, backedUp };
447
518
  }
448
519
 
449
520
  return {
450
521
  getState,
522
+ listEntries,
451
523
  setEnabled,
452
524
  refreshInstalledEntry,
453
525
  };