@oar-sh/oar 0.9.1 → 0.9.3
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/.github/extensions/web-relay/extension.mjs +0 -3
- package/.github/extensions/web-relay/polling/heartbeat.mjs +2 -55
- package/.github/extensions/web-relay/polling/polling-loop.mjs +0 -1
- package/.github/extensions/web-relay/runtime/api-client.mjs +2 -34
- package/.github/extensions/web-relay/runtime/config-loader.mjs +2 -104
- package/.github/extensions/web-relay/runtime/send-and-wait-errors.mjs +2 -170
- package/.github/extensions/web-relay/runtime/session-io.mjs +1 -71
- package/.github/extensions/web-relay/runtime/worker-websocket-link.mjs +2 -325
- package/.github/extensions/web-relay/skills/prompt-context.mjs +2 -160
- package/CHANGELOG.md +269 -0
- package/README.md +407 -424
- package/bin/oar.js +142 -11
- package/package.json +8 -4
- package/server/README.md +59 -22
- package/server/claude-worker/claude-sdk-adapter.mjs +7 -2
- package/server/claude-worker/claude-session-process.mjs +1013 -45
- package/server/claude-worker/claude-session-worker.mjs +54 -9
- package/server/claude-worker/claude-turn-publisher.mjs +110 -19
- package/server/claude-worker/claude-worker-link-wiring.mjs +9 -0
- package/server/copilot-sdk-runtime.mjs +12 -3
- package/server/copilot-worker/copilot-ask-user-tool.mjs +89 -0
- package/server/copilot-worker/copilot-attachments.mjs +35 -2
- package/server/copilot-worker/copilot-continuation-signals.mjs +55 -0
- package/server/copilot-worker/copilot-model-switch.mjs +425 -0
- package/server/copilot-worker/copilot-prompt-context.mjs +20 -8
- package/server/copilot-worker/copilot-question-bridge.mjs +129 -16
- package/server/copilot-worker/copilot-sdk-adapter.mjs +59 -11
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +136 -12
- package/server/copilot-worker/copilot-sdk-session-process.mjs +2753 -527
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +94 -25
- package/server/cursor-worker/cursor-session-worker.mjs +5 -5
- package/server/cursor-worker/cursor-turn-runner.mjs +50 -4
- package/server/db-schema.mjs +61 -1
- package/server/features.mjs +155 -41
- package/server/grok-worker/grok-session-worker.mjs +5 -5
- package/server/grok-worker/grok-turn-runner.mjs +45 -4
- package/server/migrations/0004-steer-settle-markers.mjs +119 -0
- package/server/public/app/annotate-core.mjs +181 -0
- package/server/public/app/annotate-editor.js +617 -0
- package/server/public/app/api-client.js +86 -5
- package/server/public/app/ask-user-view.js +102 -13
- package/server/public/app/attachments-view.js +172 -3
- package/server/public/app/background-task-timeout-options.mjs +9 -0
- package/server/public/app/background-tasks-view.mjs +34 -19
- package/server/public/app/bootstrap.js +153 -69
- package/server/public/app/composer-control-state.mjs +52 -34
- package/server/public/app/context-tier-options.mjs +24 -4
- package/server/public/app/conversation-draft-sync.mjs +187 -0
- package/server/public/app/conversation-list-filter.mjs +86 -0
- package/server/public/app/conversation-preferences.mjs +47 -10
- package/server/public/app/conversation-provider-indicator.mjs +8 -0
- package/server/public/app/conversation-view.js +1032 -218
- package/server/public/app/emoji-view.js +2 -0
- package/server/public/app/font-scaling.js +4 -37
- package/server/public/app/journal-view.js +190 -24
- package/server/public/app/model-selector-options.mjs +185 -1
- package/server/public/app/new-conversation-model-choice.mjs +29 -9
- package/server/public/app/pwa-install.js +64 -136
- package/server/public/app/question-multi-select.mjs +52 -0
- package/server/public/app/reasoning-effort-labels.mjs +13 -3
- package/server/public/app/relay-prompt-sanitizer.mjs +26 -3
- package/server/public/app/router.js +120 -11
- package/server/public/app/settings-modal.js +225 -20
- package/server/public/app/settings-tabs.js +1 -1
- package/server/public/app/socket-handlers.js +47 -4
- package/server/public/app/steered-turn-merge.mjs +100 -0
- package/server/public/app/store.js +159 -16
- package/server/public/app/sync-outbox.mjs +16 -8
- package/server/public/app/tool-activity-emoji.mjs +36 -0
- package/server/public/app/transcript-viewport-anchor.mjs +210 -0
- package/server/public/app/update-ui.js +194 -0
- package/server/public/index.html +483 -24
- package/server/public/sw.js +7 -0
- package/server/relay-tools.md +2 -0
- package/server/relay.mjs +23 -21
- package/server/repositories/message-repository.mjs +78 -21
- package/server/repositories/question-repository.mjs +19 -1
- package/server/repositories/session-repository.mjs +7 -31
- package/server/routes/ask-user-routes.mjs +10 -0
- package/server/routes/messages-routes.mjs +689 -159
- package/server/routes/sessions-routes.mjs +574 -80
- package/server/server-runtime.mjs +393 -925
- package/server/services/claude-auth-service.mjs +7 -1
- package/server/services/cli-install-service.mjs +8 -27
- package/server/services/cli-process-runner.mjs +30 -0
- package/server/services/copilot-model-discovery-service.mjs +202 -0
- package/server/services/data-dir-resolution.mjs +47 -0
- package/server/services/grok-auth-service.mjs +9 -1
- package/server/services/live-turn-picker.mjs +47 -0
- package/server/services/model-variant-catalog-service.mjs +1183 -0
- package/server/services/oar-cli-helpers.mjs +6 -2
- package/server/services/relay-prompt-sanitizer.mjs +30 -3
- package/server/services/relay-question-context.mjs +46 -0
- package/server/services/sdk-session-import-service.mjs +144 -16
- package/server/services/sdk-session-sync-service.mjs +84 -27
- package/server/services/session-worker-registry-service.mjs +116 -3
- package/server/services/session-worker-supervisor-service.mjs +7 -1
- package/server/services/session-worker-websocket-service.mjs +119 -24
- package/server/services/status-event-service.mjs +8 -2
- package/server/services/steer-resend.mjs +45 -0
- package/server/services/update-check-service.mjs +220 -0
- package/server/services/update-install-service.mjs +279 -0
- package/server/services/windows-autostart-service.mjs +42 -6
- package/server/services/windows-boot-autostart-service.mjs +487 -0
- package/shared/ask-user-bridge.mjs +37 -5
- package/shared/background-task-timeout.mjs +12 -4
- package/shared/claude-model-catalog.mjs +49 -0
- package/shared/context-window-fallbacks.mjs +1 -0
- package/shared/control-poller.mjs +4 -1
- package/shared/copilot-model-order.mjs +182 -0
- package/shared/media-embed-instructions.mjs +35 -0
- package/shared/model-descriptors.mjs +181 -30
- package/shared/model-id.mjs +6 -0
- package/shared/pwa-app-name.mjs +44 -0
- package/shared/question-multi-select.mjs +16 -0
- package/shared/relay-stop-failure.mjs +15 -0
- package/shared/slash-command-guard.mjs +76 -0
- package/shared/steer-note.mjs +66 -0
- package/shared/steer-settle-failure.mjs +89 -0
- package/shared/steer-settle-markers.mjs +18 -0
- package/shared/update-semver.mjs +50 -0
- package/shared/worker-crash-guard.mjs +40 -14
- package/shared/worker-runtime/api-client.mjs +34 -0
- package/shared/worker-runtime/config-loader.mjs +104 -0
- package/shared/worker-runtime/heartbeat.mjs +109 -0
- package/shared/worker-runtime/prompt-context.mjs +160 -0
- package/shared/worker-runtime/runner-link-wiring.mjs +44 -0
- package/shared/worker-runtime/send-and-wait-errors.mjs +170 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +436 -0
- package/server/claude-worker/claude-session-test-harness.mjs +0 -236
- package/server/claude-worker/fixtures/workflow-run-record.json +0 -1
- package/server/copilot-worker/copilot-sdk-test-harness.mjs +0 -329
- package/server/copilot-worker/fixtures/abort-turn.json +0 -588
- package/server/copilot-worker/fixtures/ask-user-turn.json +0 -1154
- package/server/copilot-worker/fixtures/background-timer-continuation.json +0 -445
- package/server/copilot-worker/fixtures/background-timer-turn.json +0 -497
- package/server/copilot-worker/fixtures/happy-turn.json +0 -566
- package/server/copilot-worker/fixtures/quota-turn.json +0 -647
- package/server/copilot-worker/fixtures/reasoning-turn.json +0 -625
- package/server/copilot-worker/fixtures/resume-turn.json +0 -700
- package/server/copilot-worker/fixtures/subagent-turn.json +0 -1378
- package/server/copilot-worker/fixtures/tool-permission-turn.json +0 -1293
- package/server/routes/messages-routes-test-harness.mjs +0 -111
- package/server/services/fixtures/claude-auth-stub.sh +0 -62
- package/server/services/fixtures/cli-install-stub.sh +0 -75
- package/server/services/fixtures/grok-stub.sh +0 -86
package/bin/oar.js
CHANGED
|
@@ -17,7 +17,7 @@ function resolvePackageRoot(metaUrl = import.meta.url) {
|
|
|
17
17
|
return path.resolve(path.dirname(fileURLToPath(metaUrl)), '..');
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
function parsePort(argv = []) {
|
|
20
|
+
function parsePort(argv = [], fallback = 3333) {
|
|
21
21
|
const args = Array.isArray(argv) ? argv : [];
|
|
22
22
|
for (let index = 0; index < args.length; index += 1) {
|
|
23
23
|
const arg = String(args[index] || '');
|
|
@@ -30,7 +30,12 @@ function parsePort(argv = []) {
|
|
|
30
30
|
if (Number.isInteger(parsed) && parsed > 0 && parsed <= 65535) return parsed;
|
|
31
31
|
}
|
|
32
32
|
}
|
|
33
|
-
return
|
|
33
|
+
return fallback;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function normalizePort(value) {
|
|
37
|
+
const parsed = Number.parseInt(String(value ?? ''), 10);
|
|
38
|
+
return Number.isInteger(parsed) && parsed > 0 && parsed <= 65535 ? parsed : null;
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
function readRelayLock(lockPath) {
|
|
@@ -184,7 +189,7 @@ function createDefaultConfig({ port = 3333, token = '' } = {}) {
|
|
|
184
189
|
};
|
|
185
190
|
}
|
|
186
191
|
|
|
187
|
-
function
|
|
192
|
+
function resolveConfigPaths({ packageRoot, env = process.env, layout = null } = {}) {
|
|
188
193
|
const envConfigPath = String(env.COPILOT_WEB_RELAY_CONFIG || '').trim();
|
|
189
194
|
const repoConfigPath = path.join(packageRoot, 'server', 'config.json');
|
|
190
195
|
const oarConfigPath = layout && !layout.checkout ? path.join(layout.configDir, 'config.json') : null;
|
|
@@ -195,7 +200,11 @@ function resolveLauncherConfig({ packageRoot, env = process.env, relayToken = ''
|
|
|
195
200
|
|| null
|
|
196
201
|
);
|
|
197
202
|
const managedPath = oarConfigPath || path.join(getLegacyManagedConfigDir(env), 'config.json');
|
|
198
|
-
|
|
203
|
+
return { seedPath, configPath: seedPath || managedPath };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function resolveLauncherConfig({ packageRoot, env = process.env, relayToken = '', relayPort = 3333, portOverride = null, layout = null } = {}) {
|
|
207
|
+
const { seedPath, configPath } = resolveConfigPaths({ packageRoot, env, layout });
|
|
199
208
|
const seedConfig = readJsonFile(seedPath) || {};
|
|
200
209
|
const runtimeConfig = {
|
|
201
210
|
...createDefaultConfig({ port: relayPort }),
|
|
@@ -203,10 +212,9 @@ function resolveLauncherConfig({ packageRoot, env = process.env, relayToken = ''
|
|
|
203
212
|
};
|
|
204
213
|
if (relayToken) runtimeConfig.authToken = relayToken;
|
|
205
214
|
if (!String(runtimeConfig.authToken || '').trim()) runtimeConfig.authToken = randomUUID();
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
: 3333;
|
|
215
|
+
// An explicit --port wins over the config for this run: the server gets it
|
|
216
|
+
// on argv, and only a config this command creates saves it.
|
|
217
|
+
runtimeConfig.port = normalizePort(portOverride) ?? normalizePort(runtimeConfig.port) ?? normalizePort(relayPort) ?? 3333;
|
|
210
218
|
return {
|
|
211
219
|
configPath,
|
|
212
220
|
managed: !seedPath,
|
|
@@ -262,7 +270,7 @@ export async function launchRelay({
|
|
|
262
270
|
const forwardedArgs = separatorIdx === -1 ? [] : args.slice(separatorIdx + 1);
|
|
263
271
|
if (args.includes('--help') || args.includes('-h')) {
|
|
264
272
|
logger.log([
|
|
265
|
-
'Usage: oar [--port <port>] [--
|
|
273
|
+
'Usage: oar [--port <port>] [--migrate-from <old-checkout>] [--install-extension] [--no-install-extension] [-- [gh copilot args...]]',
|
|
266
274
|
'',
|
|
267
275
|
'Starts the web relay server if needed, then launches gh copilot in the current shell.',
|
|
268
276
|
'--install-extension installs/updates a user-global web-relay wrapper extension and exits.',
|
|
@@ -284,7 +292,7 @@ export async function launchRelay({
|
|
|
284
292
|
return { code: 0, reason: 'extension-installed' };
|
|
285
293
|
}
|
|
286
294
|
|
|
287
|
-
const
|
|
295
|
+
const cliPort = parsePort(launcherArgs, null);
|
|
288
296
|
const serverDir = path.join(packageRoot, 'server');
|
|
289
297
|
const layout = resolveStateLayout({ packageRoot, env });
|
|
290
298
|
|
|
@@ -311,6 +319,12 @@ export async function launchRelay({
|
|
|
311
319
|
const lockPath = layout.checkout
|
|
312
320
|
? path.join(serverDir, 'data', 'relay-server.lock')
|
|
313
321
|
: path.join(layout.dataDir, 'relay-server.lock');
|
|
322
|
+
// One port finds, launches and probes the relay: an explicit --port for this
|
|
323
|
+
// run, else the config's, else 3333. The probe used to take --port (or 3333)
|
|
324
|
+
// while the server started on the config's port, so any mismatch waited out
|
|
325
|
+
// the readiness deadline and killed the relay it had just started.
|
|
326
|
+
const { seedPath } = resolveConfigPaths({ packageRoot, env, layout });
|
|
327
|
+
const port = cliPort ?? normalizePort(readJsonFile(seedPath)?.port) ?? 3333;
|
|
314
328
|
const statusUrl = `http://localhost:${port}/api/status`;
|
|
315
329
|
const running = await detectRunningRelay({
|
|
316
330
|
lockPath,
|
|
@@ -323,6 +337,7 @@ export async function launchRelay({
|
|
|
323
337
|
env,
|
|
324
338
|
relayToken: running?.lock?.token || '',
|
|
325
339
|
relayPort: port,
|
|
340
|
+
portOverride: cliPort,
|
|
326
341
|
layout,
|
|
327
342
|
});
|
|
328
343
|
if (runtimeConfig.managed || !fs.existsSync(runtimeConfig.configPath)) {
|
|
@@ -363,7 +378,10 @@ export async function launchRelay({
|
|
|
363
378
|
const serverErrFd = openAppendFileDescriptor(serverErrPath);
|
|
364
379
|
try {
|
|
365
380
|
serverProc = spawnImpl(nodeBin, serverArgs, {
|
|
366
|
-
|
|
381
|
+
// Global installs run from the OAR state root: on Windows a process
|
|
382
|
+
// pins its cwd, and `npm i -g` (self-update) must be able to replace
|
|
383
|
+
// the package directory. Checkouts keep the repo as cwd.
|
|
384
|
+
cwd: layout.checkout ? packageRoot : layout.root,
|
|
367
385
|
env: baseEnv,
|
|
368
386
|
stdio: ['ignore', serverOutFd, serverErrFd],
|
|
369
387
|
windowsHide: false,
|
|
@@ -640,6 +658,118 @@ async function runDoctor({ env = process.env, logger = console } = {}) {
|
|
|
640
658
|
return { code: 0, reason: 'doctor' };
|
|
641
659
|
}
|
|
642
660
|
|
|
661
|
+
/**
|
|
662
|
+
* `oar update [--beta] [--to X.Y.Z]` — the terminal twin of the web UI's
|
|
663
|
+
* Update button. Fetching the manifest here is an explicit user action, so it
|
|
664
|
+
* happens regardless of the relay's opt-in auto-check setting.
|
|
665
|
+
*/
|
|
666
|
+
async function runUpdate({ argv = [], env = process.env, logger = console, fetchImpl = globalThis.fetch, spawnImpl = spawn } = {}) {
|
|
667
|
+
const { channelForVersion, compareSemverIsh, parseSemverIsh } = await import('../shared/update-semver.mjs');
|
|
668
|
+
const packageRoot = resolvePackageRoot();
|
|
669
|
+
const runningVersion = readPackageVersion(packageRoot);
|
|
670
|
+
|
|
671
|
+
if (isGitCheckout(packageRoot)) {
|
|
672
|
+
logger.error('[oar] This install runs from a git checkout — update with git pull, then restart the relay.');
|
|
673
|
+
return { code: 1, reason: 'git-checkout' };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
const toIdx = argv.indexOf('--to');
|
|
677
|
+
let target = toIdx !== -1 ? String(argv[toIdx + 1] || '').trim() : '';
|
|
678
|
+
if (target && !parseSemverIsh(target)) {
|
|
679
|
+
logger.error(`[oar] --to expects a release version like 0.9.2, got "${target}".`);
|
|
680
|
+
return { code: 1, reason: 'bad-target' };
|
|
681
|
+
}
|
|
682
|
+
if (!target) {
|
|
683
|
+
// The kill switch means "never phone home from this host", so it blocks
|
|
684
|
+
// the manifest fetch too — an explicit `--to X.Y.Z` still works because
|
|
685
|
+
// that path talks only to the npm registry the install already uses.
|
|
686
|
+
if (String(env.OAR_NO_UPDATE_CHECK || '').trim() === '1') {
|
|
687
|
+
logger.error('[oar] Update checks are disabled on this host (OAR_NO_UPDATE_CHECK). Use `oar update --to X.Y.Z` to update to a known version.');
|
|
688
|
+
return { code: 1, reason: 'check-killed' };
|
|
689
|
+
}
|
|
690
|
+
const manifestUrl = String(env.OAR_UPDATE_MANIFEST_URL || '').trim() || 'https://oar.sh/latest.json';
|
|
691
|
+
let manifest = null;
|
|
692
|
+
try {
|
|
693
|
+
const response = await fetchImpl(manifestUrl, { signal: AbortSignal.timeout?.(5000) });
|
|
694
|
+
manifest = response?.ok ? await response.json() : null;
|
|
695
|
+
} catch {}
|
|
696
|
+
if (!manifest) {
|
|
697
|
+
logger.error(`[oar] Could not fetch ${manifestUrl}.`);
|
|
698
|
+
return { code: 1, reason: 'manifest-unreachable' };
|
|
699
|
+
}
|
|
700
|
+
if (manifest.schemaVersion !== 1) {
|
|
701
|
+
logger.error(`[oar] ${manifestUrl} has schemaVersion ${manifest.schemaVersion}, which this oar does not understand — update manually.`);
|
|
702
|
+
return { code: 1, reason: 'manifest-unsupported' };
|
|
703
|
+
}
|
|
704
|
+
const channel = argv.includes('--beta') ? 'beta' : channelForVersion(runningVersion);
|
|
705
|
+
target = String(manifest.channels?.[channel]?.version || manifest.channels?.stable?.version || '').trim();
|
|
706
|
+
if (!parseSemverIsh(target)) {
|
|
707
|
+
logger.error('[oar] The update manifest carries no usable version.');
|
|
708
|
+
return { code: 1, reason: 'manifest-empty' };
|
|
709
|
+
}
|
|
710
|
+
if (compareSemverIsh(target, runningVersion) <= 0) {
|
|
711
|
+
logger.log(`[oar] Already up to date (${runningVersion}).`);
|
|
712
|
+
return { code: 0, reason: 'up-to-date' };
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
logger.log(`[oar] Updating @oar-sh/oar ${runningVersion} -> ${target} ...`);
|
|
717
|
+
const npmCommand = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
718
|
+
const exitCode = await new Promise((resolve) => {
|
|
719
|
+
const child = spawnImpl(npmCommand, ['install', '-g', `@oar-sh/oar@${target}`], {
|
|
720
|
+
stdio: 'inherit',
|
|
721
|
+
...(process.platform === 'win32' ? { shell: true, windowsHide: true } : {}),
|
|
722
|
+
});
|
|
723
|
+
child.on('error', (error) => {
|
|
724
|
+
logger.error(`[oar] npm spawn failed: ${error?.message || error}`);
|
|
725
|
+
resolve(1);
|
|
726
|
+
});
|
|
727
|
+
child.on('close', (code) => resolve(code ?? 1));
|
|
728
|
+
});
|
|
729
|
+
if (exitCode !== 0) {
|
|
730
|
+
logger.error('[oar] npm install failed; the running install is unchanged.');
|
|
731
|
+
return { code: 1, reason: 'npm-failed' };
|
|
732
|
+
}
|
|
733
|
+
logger.log(`[oar] Installed @oar-sh/oar@${target}.`);
|
|
734
|
+
|
|
735
|
+
// A running relay picks the new code up on restart; ask for the queue-idle
|
|
736
|
+
// deferred one so no in-flight turn is cut off.
|
|
737
|
+
const layout = resolveStateLayout({ packageRoot, env });
|
|
738
|
+
const configPath = layout.checkout
|
|
739
|
+
? path.join(packageRoot, 'server', 'config.json')
|
|
740
|
+
: path.join(layout.configDir, 'config.json');
|
|
741
|
+
const config = readJsonFile(configPath);
|
|
742
|
+
const port = Number(config?.port) || 3333;
|
|
743
|
+
const lockPath = layout.checkout
|
|
744
|
+
? path.join(packageRoot, 'server', 'data', 'relay-server.lock')
|
|
745
|
+
: path.join(layout.dataDir, 'relay-server.lock');
|
|
746
|
+
const running = await detectRunningRelay({ lockPath, statusUrl: `http://localhost:${port}/api/status`, fetchImpl });
|
|
747
|
+
if (!running.running) {
|
|
748
|
+
logger.log('[oar] No running relay detected — the update loads on the next start.');
|
|
749
|
+
return { code: 0, reason: 'updated' };
|
|
750
|
+
}
|
|
751
|
+
const token = String(running.lock?.token || config?.authToken || '').trim();
|
|
752
|
+
try {
|
|
753
|
+
const response = await fetchImpl(`http://localhost:${port}/api/relay/shutdown`, {
|
|
754
|
+
method: 'POST',
|
|
755
|
+
headers: {
|
|
756
|
+
'Content-Type': 'application/json',
|
|
757
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
758
|
+
},
|
|
759
|
+
body: JSON.stringify({ reason: 'self-update-cli', requestedBy: 'oar-update', restart: true }),
|
|
760
|
+
signal: AbortSignal.timeout?.(5000),
|
|
761
|
+
});
|
|
762
|
+
if (response?.ok) {
|
|
763
|
+
logger.log('[oar] Relay restart requested — it restarts once the queue is idle.');
|
|
764
|
+
} else {
|
|
765
|
+
logger.log('[oar] Could not request a relay restart; restart it manually to load the update.');
|
|
766
|
+
}
|
|
767
|
+
} catch {
|
|
768
|
+
logger.log('[oar] Could not request a relay restart; restart it manually to load the update.');
|
|
769
|
+
}
|
|
770
|
+
return { code: 0, reason: 'updated' };
|
|
771
|
+
}
|
|
772
|
+
|
|
643
773
|
function main() {
|
|
644
774
|
const argv = process.argv.slice(2);
|
|
645
775
|
const command = String(argv[0] || '').trim();
|
|
@@ -649,6 +779,7 @@ function main() {
|
|
|
649
779
|
}
|
|
650
780
|
if (command === 'setup') return runSetup({ argv: argv.slice(1) });
|
|
651
781
|
if (command === 'doctor') return runDoctor({});
|
|
782
|
+
if (command === 'update') return runUpdate({ argv: argv.slice(1) });
|
|
652
783
|
return launchRelay();
|
|
653
784
|
}
|
|
654
785
|
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oar-sh/oar",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.3",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "OAR
|
|
5
|
+
"description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://oar.sh",
|
|
8
8
|
"repository": {
|
|
@@ -20,6 +20,8 @@
|
|
|
20
20
|
"bin/",
|
|
21
21
|
"server/",
|
|
22
22
|
"!server/**/*.test.mjs",
|
|
23
|
+
"!server/**/*-test-harness.mjs",
|
|
24
|
+
"!server/**/fixtures/",
|
|
23
25
|
"!server/data/",
|
|
24
26
|
"!server/logs/",
|
|
25
27
|
"!server/config.json",
|
|
@@ -28,6 +30,7 @@
|
|
|
28
30
|
"server/uploads/image_input.md",
|
|
29
31
|
"shared/",
|
|
30
32
|
"!shared/**/*.test.mjs",
|
|
33
|
+
"!shared/**/fixtures/",
|
|
31
34
|
".github/extensions/web-relay/",
|
|
32
35
|
"!.github/extensions/web-relay/**/*.test.mjs",
|
|
33
36
|
"README.md",
|
|
@@ -43,8 +46,9 @@
|
|
|
43
46
|
"test:e2e:raw": "node ./node_modules/@playwright/test/cli.js test --config tests/playwright.config.mjs"
|
|
44
47
|
},
|
|
45
48
|
"dependencies": {
|
|
46
|
-
"@anthropic-ai/claude-agent-sdk": "^0.3.
|
|
47
|
-
"@cursor/sdk": "^1.0.
|
|
49
|
+
"@anthropic-ai/claude-agent-sdk": "^0.3.283",
|
|
50
|
+
"@cursor/sdk": "^1.0.32",
|
|
51
|
+
"@github/copilot-sdk": "^1.0.14",
|
|
48
52
|
"better-sqlite3": "^12.10.0",
|
|
49
53
|
"express": "^4.18.2",
|
|
50
54
|
"qrcode-terminal": "^0.12.0",
|
package/server/README.md
CHANGED
|
@@ -194,8 +194,12 @@ When opened in a regular browser tab, the in-app **Install** button remains avai
|
|
|
194
194
|
in the chat header (shown as `⬇` on small screens).
|
|
195
195
|
|
|
196
196
|
You can rename the installed app label from **⚙️ Settings → Install app name**.
|
|
197
|
-
The
|
|
198
|
-
|
|
197
|
+
The name is stored on the relay (app settings) and served in the web app manifest
|
|
198
|
+
to every browser and origin (LAN and tunnel agree), so future installs pick it up
|
|
199
|
+
immediately and already-installed Android apps receive it through Chrome's normal
|
|
200
|
+
manifest-update flow — one confirmation prompt showing the new name, then the
|
|
201
|
+
launcher label updates. A legacy per-browser name is adopted by the relay the
|
|
202
|
+
first time that browser loads the app.
|
|
199
203
|
|
|
200
204
|
You can also hide the **💤 Suspend host** action from **⚙️ Settings** with the
|
|
201
205
|
**Show Suspend host action** checkbox. This only controls UI visibility; it does not
|
|
@@ -523,25 +527,58 @@ cards with a reply box.
|
|
|
523
527
|
The active conversation header includes a `✍️` button for renaming the conversation inline.
|
|
524
528
|
Title edits are saved to the database and broadcast to other open clients immediately.
|
|
525
529
|
|
|
526
|
-
##
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
530
|
+
## Updates
|
|
531
|
+
|
|
532
|
+
OAR never phones home by default. Automatic update checking is **opt-in**:
|
|
533
|
+
enable **Check for updates automatically** in ⚙️ Settings (stored in app
|
|
534
|
+
settings, off unless you turn it on) and the relay asks `oar.sh/latest.json`
|
|
535
|
+
for the newest version about twice a day (12 h ± 1 h jitter, ETag-aware, 5 s
|
|
536
|
+
timeout, failures silent). A manual **Check for updates** button works without
|
|
537
|
+
opting in — that single request is the only traffic it creates. Set
|
|
538
|
+
`OAR_NO_UPDATE_CHECK=1` in the relay's environment to disable even manual
|
|
539
|
+
checks; `OAR_UPDATE_MANIFEST_URL` points the check at a different manifest
|
|
540
|
+
(used by tests and staging).
|
|
541
|
+
|
|
542
|
+
When a newer version is available, Settings shows a dismissible-per-version
|
|
543
|
+
card (not dismissible if the release is marked critical — it still never
|
|
544
|
+
auto-applies). On npm-global installs, **Update** runs
|
|
545
|
+
`npm i -g @oar-sh/oar@<version>` with the log streamed into the card, then the
|
|
546
|
+
relay restarts once the message queue is idle. After the restart the relay
|
|
547
|
+
verifies the running version actually changed; if not, the npm log is surfaced
|
|
548
|
+
as a failure. Git checkouts are detect-only — the card shows a "pull to
|
|
549
|
+
update" hint instead of the button. `oar update [--beta] [--to X.Y.Z]` does
|
|
550
|
+
the same from a terminal, and rollback is `oar update --to <previous>`.
|
|
551
|
+
|
|
552
|
+
## Feature flags
|
|
553
|
+
|
|
554
|
+
Feature flags are declared in one registry (`server/features.mjs`) and managed
|
|
555
|
+
from the web UI's Settings → Features tab, which renders each flag with its
|
|
556
|
+
user-facing description. Current flags and defaults:
|
|
557
|
+
|
|
558
|
+
- `SESSION_WORKER_ROUTING_ENABLED` — ON by default; per-conversation worker
|
|
559
|
+
processes with ownership and crash recovery (off = legacy shared polling).
|
|
560
|
+
- `SESSION_WORKER_CONTINUATION_ROUTING_ENABLED` — ON by default; strict
|
|
561
|
+
ownership validation when routing clarification answers back to the asking
|
|
562
|
+
worker.
|
|
563
|
+
- `SESSION_WORKER_FALLBACK_RESTART_ENABLED` — OFF by default; reserved rollout
|
|
564
|
+
gate, no effect yet.
|
|
565
|
+
- `IMAGE_CONVERSATION_CONTINUITY_ENABLED` — ON by default; keeps
|
|
566
|
+
generated-image context across turns so *Edit this image* can continue from
|
|
567
|
+
the right source image.
|
|
568
|
+
|
|
569
|
+
Resolution happens once at relay startup, with this precedence:
|
|
570
|
+
|
|
571
|
+
1. Registry defaults
|
|
572
|
+
2. Stored settings (`app_settings` rows, written by the Features tab via
|
|
573
|
+
`GET/POST /api/settings/features`)
|
|
574
|
+
3. Environment override: `COPILOT_REMOTE_{FLAG_NAME}` (`true/false`, `1/0`,
|
|
575
|
+
`yes/no`, `on/off`)
|
|
576
|
+
|
|
577
|
+
Changes saved in the Features tab take effect after a relay restart; the tab
|
|
578
|
+
shows a restart notice and offers the queue-idle-deferred restart. A legacy
|
|
579
|
+
`features` key in `config.json` is migrated into `app_settings` once at boot
|
|
580
|
+
and stripped from the file. Unknown flag names and invalid values are ignored
|
|
581
|
+
safely.
|
|
545
582
|
|
|
546
583
|
Question bridge rule:
|
|
547
584
|
|
|
@@ -736,7 +773,7 @@ Queue metrics include `parkedCount` for turns deferred behind restart/rebind gat
|
|
|
736
773
|
| GET | `/api/search/messages` | Search message text across all conversations (`q`, `limit`, `offset`) |
|
|
737
774
|
| PATCH | `/api/conversation/:id` | Update a conversation title |
|
|
738
775
|
| POST | `/api/conversation/:id/compact` | Compact a conversation into a new one with carry-over summary seed |
|
|
739
|
-
| DELETE | `/api/conversation/:id` | Delete a conversation |
|
|
776
|
+
| DELETE | `/api/conversation/:id` | Delete a conversation: stops its session worker, deletes its CLI session, removes its rows. A Copilot session the relay only imported and never ran is hidden and tombstoned instead: nothing is stopped and its CLI session is kept. `409 conversation-active` (`reason: turn-running` / `background-tasks`) while it is still working; `409 worker-stop-failed` when its worker survives the stop (nothing deleted) |
|
|
740
777
|
| GET | `/api/sdk-session-delete/pending` | (CLI relay) Fetch next pending SDK session delete request |
|
|
741
778
|
| POST | `/api/sdk-session-delete/result` | (CLI relay) Report SDK session delete result |
|
|
742
779
|
| POST | `/api/session-sync` | (CLI relay) Sync conversation↔SDK binding and optionally confirm orchestrator rebind completion |
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createSdkMcpServer, query } from '@anthropic-ai/claude-agent-sdk';
|
|
2
2
|
|
|
3
3
|
import { parseThinkingDisplay } from '../../shared/claude-thinking.mjs';
|
|
4
|
+
import { MEDIA_EMBED_INSTRUCTION_TEXT } from '../../shared/media-embed-instructions.mjs';
|
|
4
5
|
import { createPreviewToolDefinition } from './claude-preview-tool.mjs';
|
|
5
6
|
|
|
6
7
|
// The name the in-process MCP server is registered under; it prefixes every
|
|
@@ -122,11 +123,15 @@ export function permissionModeForRelayMode(relayMode) {
|
|
|
122
123
|
|
|
123
124
|
export function systemPromptForRelayMode(relayMode) {
|
|
124
125
|
const mode = String(relayMode || 'agent').trim().toLowerCase();
|
|
125
|
-
|
|
126
|
+
// The media-embed capability rides every session — unlike the mode nudges it
|
|
127
|
+
// is renderer knowledge the model cannot discover any other way.
|
|
128
|
+
const append = [MEDIA_EMBED_INSTRUCTION_TEXT, MODE_SYSTEM_PROMPT_APPEND[mode]]
|
|
129
|
+
.filter(Boolean)
|
|
130
|
+
.join('\n\n');
|
|
126
131
|
return {
|
|
127
132
|
type: 'preset',
|
|
128
133
|
preset: 'claude_code',
|
|
129
|
-
|
|
134
|
+
append,
|
|
130
135
|
};
|
|
131
136
|
}
|
|
132
137
|
|