@oar-sh/oar 0.9.22 → 0.9.24

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 (68) hide show
  1. package/CHANGELOG.md +258 -0
  2. package/README.md +9 -9
  3. package/bin/oar.js +82 -4
  4. package/package.json +16 -15
  5. package/server/agent-sdk-loader.mjs +116 -0
  6. package/server/agent-sdks/claude/entry.mjs +4 -0
  7. package/server/agent-sdks/claude/package-lock.json +1438 -0
  8. package/server/agent-sdks/claude/package.json +13 -0
  9. package/server/agent-sdks/cursor/entry-sqlite.mjs +2 -0
  10. package/server/agent-sdks/cursor/entry.mjs +3 -0
  11. package/server/agent-sdks/cursor/package-lock.json +171 -0
  12. package/server/agent-sdks/cursor/package.json +11 -0
  13. package/server/agent-sdks/koffi/entry.cjs +2 -0
  14. package/server/agent-sdks/koffi/package-lock.json +368 -0
  15. package/server/agent-sdks/koffi/package.json +10 -0
  16. package/server/claude-worker/claude-cli-pinned-env.mjs +4 -1
  17. package/server/claude-worker/claude-preview-tool.mjs +5 -2
  18. package/server/claude-worker/claude-remote-relay-tool.mjs +5 -2
  19. package/server/claude-worker/claude-sdk-adapter.mjs +4 -1
  20. package/server/copilot-worker/copilot-sdk-session-process.mjs +7 -4
  21. package/server/cursor-worker/cursor-sdk-adapter.mjs +5 -3
  22. package/server/public/app/agent-sdk-ui.js +349 -0
  23. package/server/public/app/api-client.js +91 -8
  24. package/server/public/app/bootstrap.js +204 -69
  25. package/server/public/app/model-refresh-ui.mjs +413 -0
  26. package/server/public/app/no-conversation-state.mjs +5 -2
  27. package/server/public/app/provider-readiness.mjs +67 -4
  28. package/server/public/app/settings-modal.js +167 -0
  29. package/server/public/app/socket-handlers.js +21 -0
  30. package/server/public/index.html +95 -2
  31. package/server/routes/agent-sdk-routes.mjs +73 -0
  32. package/server/routes/messages-routes.mjs +102 -37
  33. package/server/routes/sessions-routes.mjs +236 -110
  34. package/server/server-runtime.mjs +416 -121
  35. package/server/services/about-service.mjs +26 -6
  36. package/server/services/agent-sdk-launch-gate.mjs +124 -0
  37. package/server/services/agent-sdk-paths.mjs +307 -0
  38. package/server/services/agent-sdk-store.mjs +1211 -0
  39. package/server/services/claude-api-settings-service.mjs +54 -17
  40. package/server/services/cli-install-service.mjs +3 -2
  41. package/server/services/copilot-model-discovery-service.mjs +7 -1
  42. package/server/services/database-file.mjs +17 -1
  43. package/server/services/deepseek-settings-service.mjs +58 -20
  44. package/server/services/earlier-database-service.mjs +5 -3
  45. package/server/services/file-serving.mjs +13 -0
  46. package/server/services/model-refresh-scheduler.mjs +153 -0
  47. package/server/services/model-variant-catalog-service.mjs +141 -9
  48. package/server/services/native-addon-build-service.mjs +224 -0
  49. package/server/services/oar-cli-helpers.mjs +76 -10
  50. package/server/services/oar-state-migration-service.mjs +2 -12
  51. package/server/services/oar-state-root.mjs +17 -0
  52. package/server/services/provider-model-refresh.mjs +326 -0
  53. package/server/services/provider-model-state.mjs +395 -0
  54. package/server/services/relay-console-log.mjs +2 -3
  55. package/server/services/relay-singleton-guard.mjs +72 -10
  56. package/server/services/remote-relay-loopback.mjs +3 -3
  57. package/server/services/sdk-session-sync-service.mjs +1 -1
  58. package/server/services/session-worker-launch-service.mjs +13 -0
  59. package/server/services/session-worker-supervisor-service.mjs +19 -1
  60. package/server/services/session-worker-websocket-service.mjs +15 -0
  61. package/server/services/update-install-service.mjs +34 -3
  62. package/server/services/worker-launch-note-service.mjs +14 -2
  63. package/server/services/worker-recycle-service.mjs +145 -11
  64. package/shared/model-refresh-schedule.mjs +120 -0
  65. package/shared/worker-runtime/direct-openai-image.mjs +8 -4
  66. package/server/services/package-data-rescue-preinstall.mjs +0 -22
  67. package/server/services/package-data-rescue.mjs +0 -139
  68. package/server/tty-console-bootstrap.mjs +0 -75
@@ -1,139 +0,0 @@
1
- /**
2
- * The preinstall's work (package-data-rescue-preinstall.mjs), on every
3
- * platform. Relays before 0.9.14 could run on the database and uploads
4
- * inside the package (Windows autostart, a plain `gh copilot`), and
5
- * `npm i -g` deletes the old package. While install scripts run, npm keeps
6
- * it as its retired folder (npmRetiredPath; a scan for npm-shaped names only
7
- * when that is missing): its database (-wal, -shm), Cursor stores, uploads
8
- * and config.json are copied, copy only, to
9
- * `<data folder>/rescued-package-data/<time>/` (written as `.partial`, then
10
- * renamed), and the relay takes them over at its next start. Any of them
11
- * makes the old package a source (its uploads beyond the README.md and
12
- * image_input.md it ships). Nothing to copy, or not an installed package:
13
- * nothing happens, nothing is printed. A failed copy throws
14
- * RescueCopyError: exit 1, npm puts the old package back. npm hides a successful script's output, hence the README.txt.
15
- */
16
- import crypto from 'crypto';
17
- import fs from 'fs';
18
- import path from 'path';
19
-
20
- import { DB_FILE_NAMES, sideFiles } from './database-file.mjs';
21
- import { RESCUE_DIR_NAME, copyMissing, isRetiredName } from './earlier-database-service.mjs';
22
- import { resolveRelayStateDirs, resolveStateLayout } from './oar-state-layout.mjs';
23
-
24
- // Old packages only ever held the name before 0.9.20; a later one may hold oar.db.
25
- const DB_FILES = DB_FILE_NAMES.flatMap(sideFiles);
26
-
27
- // What a relay left in a package's server folder; a folder that cannot be
28
- // read throws (no copy failure: nothing was detected yet).
29
- function hasRelayData(fsImpl, serverDir) {
30
- const has = (...parts) => fsImpl.existsSync(path.join(serverDir, ...parts));
31
- const anyUpload = (...parts) => has(...parts) && fsImpl.readdirSync(path.join(serverDir, ...parts)).some((name) => /^[a-f0-9]{64}$/.test(name));
32
- return DB_FILE_NAMES.some((name) => has('data', name)) || has('data', 'cursor-agents') || has('config.json') || anyUpload('uploads') || anyUpload('data', 'uploads');
33
- }
34
-
35
- /** Where npm's reify moves a package it replaces (@npmcli/arborist retire-path.js). */
36
- export function npmRetiredPath(packageDir) {
37
- const hash = crypto.createHash('sha1').update(packageDir).digest('base64').replace(/[^a-zA-Z0-9]+/g, '').slice(0, 8);
38
- return path.resolve(path.dirname(packageDir), `.${path.basename(packageDir)}-${hash}`);
39
- }
40
-
41
- /** A copy of an old database failed: the one error that stops the install. */
42
- export class RescueCopyError extends Error {
43
- constructor(message, options) {
44
- super(message, options);
45
- this.name = 'RescueCopyError';
46
- this.rescueCopyFailed = true;
47
- }
48
- }
49
-
50
- /**
51
- * Returns { status: 'not-installed' | 'nothing' | 'rescued', rescued: [{ from,
52
- * to }] }; throws a RescueCopyError, naming what to save, when a copy fails.
53
- */
54
- export function rescuePackageData({
55
- packageRoot,
56
- env = process.env,
57
- platform = process.platform,
58
- now = () => new Date(),
59
- fsImpl = fs,
60
- logger = console,
61
- } = {}) {
62
- const resolvedRoot = path.resolve(String(packageRoot || ''));
63
- const scope = path.dirname(resolvedRoot);
64
- if (path.basename(scope) !== '@oar-sh' || path.basename(path.dirname(scope)) !== 'node_modules') {
65
- return { status: 'not-installed', rescued: [] };
66
- }
67
- // Detection: npm's retired name, else a scan for npm-shaped names (a path
68
- // in another case hashes differently). Failing here is no copy failure.
69
- let sources;
70
- try {
71
- const exact = npmRetiredPath(resolvedRoot);
72
- const retired = fsImpl.existsSync(exact)
73
- ? [exact]
74
- : fsImpl.readdirSync(scope).filter((name) => isRetiredName(name, path.basename(resolvedRoot))).map((name) => path.join(scope, name));
75
- sources = retired.filter((dir) => hasRelayData(fsImpl, path.join(dir, 'server')));
76
- } catch (error) {
77
- throw new Error(`Could not look for the data of the version being replaced: ${error?.message || error}`, { cause: error });
78
- }
79
- if (!sources.length) return { status: 'nothing', rescued: [] };
80
-
81
- // Old data is there: any failure from here stops the install.
82
- const exists = (file) => fsImpl.existsSync(file);
83
- const kept = path.join(resolvedRoot, 'server');
84
- const copyFailed = (from, to, error) => new RescueCopyError(
85
- `Could not copy the data of the version being replaced (${from}) to ${to}: ${error?.message || error}. `
86
- + `The update stops and the version installed stays as it is; copy ${path.join(kept, 'data')} and ${path.join(kept, 'uploads')} somewhere safe by hand, then update again.`,
87
- { cause: error },
88
- );
89
- let rescueRoot;
90
- try {
91
- const layout = resolveStateLayout({ packageRoot: resolvedRoot, env, platform });
92
- rescueRoot = path.join(resolveRelayStateDirs({ packageRoot: resolvedRoot, env, platform, layout }).dataDir, RESCUE_DIR_NAME);
93
- } catch (error) {
94
- throw copyFailed(path.join(sources[0], 'server'), 'the OAR data folder', error);
95
- }
96
- const stamp = now().toISOString().replace(/[:.]/g, '-');
97
- const rescued = [];
98
- for (const [index, source] of sources.entries()) {
99
- const serverDir = path.join(source, 'server');
100
- let target = path.join(rescueRoot, sources.length > 1 ? `${stamp}-${index}` : stamp);
101
- // Written under a .partial name and renamed when complete: a half copy
102
- // is never taken for a rescue.
103
- try {
104
- for (let n = 1; exists(target) || exists(`${target}.partial`); n += 1) target = `${path.join(rescueRoot, stamp)}-${n}`;
105
- const partial = `${target}.partial`;
106
- fsImpl.mkdirSync(rescueRoot, { recursive: true });
107
- fsImpl.mkdirSync(partial);
108
- fsImpl.mkdirSync(path.join(partial, 'data'));
109
- for (const name of DB_FILES) {
110
- const from = path.join(serverDir, 'data', name);
111
- if (exists(from)) fsImpl.copyFileSync(from, path.join(partial, 'data', name), fs.constants.COPYFILE_EXCL);
112
- }
113
- if (exists(path.join(serverDir, 'data', 'cursor-agents'))) {
114
- copyMissing(fsImpl, path.join(serverDir, 'data', 'cursor-agents'), path.join(partial, 'data', 'cursor-agents'));
115
- }
116
- // Uploads of server/uploads and server/data/uploads, content-addressed.
117
- for (const uploads of [path.join(serverDir, 'uploads'), path.join(serverDir, 'data', 'uploads')]) {
118
- if (exists(uploads)) copyMissing(fsImpl, uploads, path.join(partial, 'uploads'));
119
- }
120
- if (exists(path.join(serverDir, 'config.json'))) {
121
- fsImpl.copyFileSync(path.join(serverDir, 'config.json'), path.join(partial, 'config.json'), fs.constants.COPYFILE_EXCL);
122
- }
123
- fsImpl.writeFileSync(path.join(partial, 'README.txt'), [
124
- `Copied by the @oar-sh/oar update on ${now().toISOString()} from`,
125
- `${serverDir}`,
126
- 'before npm deleted the version it replaced. The relay takes the',
127
- 'database in data\\ over at its next start if it is newer than the',
128
- 'one it has. Nothing here is deleted by OAR.',
129
- '',
130
- ].join('\r\n'));
131
- fsImpl.renameSync(partial, target);
132
- } catch (error) {
133
- throw copyFailed(serverDir, target, error);
134
- }
135
- rescued.push({ from: serverDir, to: target });
136
- logger.log?.(`[oar] Copied the data of the version being replaced from ${serverDir} to ${target}; the relay takes it over at its next start.`);
137
- }
138
- return { status: 'rescued', rescued };
139
- }
@@ -1,75 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { createRequire } from 'module';
4
-
5
- function existingDirectories(paths = []) {
6
- const out = [];
7
- for (const candidate of paths) {
8
- const normalized = String(candidate || '').trim();
9
- if (!normalized) continue;
10
- try {
11
- if (fs.statSync(normalized).isDirectory()) out.push(normalized);
12
- } catch {}
13
- }
14
- return out;
15
- }
16
-
17
- function resolveTtyConsoleRoot() {
18
- const require = createRequire(import.meta.url);
19
- return path.dirname(require.resolve('tty-console'));
20
- }
21
-
22
- export async function maybeStartTtyConsole({
23
- serverDir,
24
- logsDir,
25
- logger = console,
26
- } = {}) {
27
- if (!process.stdin.isTTY || !process.stdout.isTTY) return null;
28
-
29
- const resolvedServerDir = path.resolve(String(serverDir || process.cwd()).trim() || process.cwd());
30
- const resolvedLogsDir = path.resolve(String(logsDir || path.join(resolvedServerDir, 'logs')).trim() || path.join(resolvedServerDir, 'logs'));
31
- fs.mkdirSync(resolvedLogsDir, { recursive: true });
32
-
33
- let ttyConsoleModule;
34
- try {
35
- ttyConsoleModule = await import('tty-console');
36
- } catch (error) {
37
- const code = String(error?.code || '').trim();
38
- if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') {
39
- logger?.log?.('[server] tty-console not installed; continuing without terminal console.');
40
- return null;
41
- }
42
- logger?.warn?.(`[server] tty-console load failed: ${error?.message || error}`);
43
- return null;
44
- }
45
-
46
- const ttyConsoleApi = ttyConsoleModule?.default || ttyConsoleModule;
47
- const ConsoleCtor = ttyConsoleApi?.Console;
48
- if (typeof ConsoleCtor !== 'function') {
49
- logger?.warn?.('[server] tty-console loaded but Console export was unavailable.');
50
- return null;
51
- }
52
-
53
- const ttyConsoleRoot = resolveTtyConsoleRoot();
54
- const bundledCommandsDir = path.join(ttyConsoleRoot, 'commands');
55
- const localCommandsDir = path.join(resolvedServerDir, 'commands');
56
- const commandsDirs = existingDirectories([bundledCommandsDir, localCommandsDir]);
57
- const configPath = path.join(resolvedLogsDir, 'console_config.json');
58
- const historyPath = path.join(resolvedLogsDir, 'console_history.json');
59
-
60
- const consoleOptions = {
61
- configPath,
62
- historyPath,
63
- exitOnStop: false,
64
- };
65
- if (commandsDirs.length === 1) {
66
- consoleOptions.commandsDir = commandsDirs[0];
67
- } else if (commandsDirs.length > 1) {
68
- consoleOptions.commandsDir = commandsDirs;
69
- }
70
-
71
- const runtime = new ConsoleCtor(consoleOptions);
72
- runtime.start?.();
73
- logger?.log?.(`[server] tty-console started (${commandsDirs.join(', ')})`);
74
- return runtime;
75
- }