@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.
- package/CHANGELOG.md +179 -0
- package/README.md +9 -9
- package/bin/oar.js +525 -12
- package/package.json +3 -1
- package/server/claude-worker/claude-remote-relay-tool.mjs +36 -1
- package/server/claude-worker/claude-session-process.mjs +4 -4
- package/server/copilot-worker/copilot-sdk-session-process.mjs +2 -2
- package/server/db-schema.mjs +52 -0
- package/server/migrations/0019-relay-event-subscription-options.mjs +106 -0
- package/server/migrations/0020-conversation-may-create-sessions.mjs +67 -0
- package/server/migrations/0021-conversation-group-agent-marks.mjs +77 -0
- package/server/migrations/0022-relay-schedules.mjs +142 -0
- package/server/migrations/0023-relay-asks.mjs +141 -0
- package/server/public/app/ask-user-view.js +3 -0
- package/server/public/app/bootstrap.js +61 -3
- package/server/public/app/context-usage-view.mjs +17 -0
- package/server/public/app/conversation-group-order.mjs +35 -0
- package/server/public/app/inbox-ui.js +356 -0
- package/server/public/app/inbox-view.mjs +370 -0
- package/server/public/app/journal-view.js +23 -2
- package/server/public/app/relay-event-subscriptions-ui.js +4 -2
- package/server/public/app/relay-event-view.mjs +33 -7
- package/server/public/app/relay-schedule-view.mjs +119 -0
- package/server/public/app/relay-schedules-ui.js +172 -0
- package/server/public/app/settings-modal.js +17 -3
- package/server/public/app/socket-handlers.js +27 -3
- package/server/public/app/store.js +18 -5
- package/server/public/index.html +159 -2
- package/server/relay-tools.md +18 -3
- package/server/repositories/conversation-group-repository.mjs +97 -11
- package/server/repositories/remote-relay-repository.mjs +20 -2
- package/server/repositories/session-repository.mjs +7 -1
- package/server/routes/ask-user-routes.mjs +57 -3
- package/server/routes/cache-routes.mjs +6 -0
- package/server/routes/conversation-group-routes.mjs +17 -2
- package/server/routes/messages-routes.mjs +3 -3
- package/server/routes/relay-ask-routes.mjs +387 -0
- package/server/routes/relay-event-routes.mjs +3 -1
- package/server/routes/relay-schedule-routes.mjs +57 -0
- package/server/routes/sessions-routes.mjs +613 -56
- package/server/server-runtime.mjs +80 -1
- package/server/services/cache-rebuild-service.mjs +19 -2
- package/server/services/conversation-preferences-service.mjs +17 -0
- package/server/services/oar-cli-helpers.mjs +12 -4
- package/server/services/relay-ask-link-service.mjs +174 -0
- package/server/services/relay-ask-service.mjs +604 -0
- package/server/services/relay-event-service.mjs +589 -73
- package/server/services/relay-schedule-service.mjs +596 -0
- package/server/services/remote-relay-caller-context.mjs +89 -20
- package/server/services/remote-relay-dispatcher.mjs +956 -30
- package/server/services/windows-autostart-service.mjs +88 -16
- package/server/services/windows-boot-autostart-service.mjs +775 -126
- package/shared/relay-asks.mjs +145 -0
- package/shared/relay-events.mjs +268 -18
- package/shared/relay-schedules.mjs +128 -0
- package/shared/remote-relay-contract.mjs +553 -25
- package/shared/remote-relay-tool-core.mjs +16 -5
- package/.github/extensions/web-relay/README.md +0 -190
- 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
|
-
|
|
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
|
|
430
|
-
|
|
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
|
|
433
|
-
|
|
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
|
-
|
|
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:
|
|
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
|
};
|