@oar-sh/oar 0.9.12 → 0.9.14
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 +9 -0
- package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +15 -1
- package/.github/extensions/web-relay/skills/question-bridge.mjs +4 -1
- package/CHANGELOG.md +508 -0
- package/README.md +12 -12
- package/bin/oar.js +155 -94
- package/package.json +5 -5
- package/server/README.md +479 -157
- package/server/claude-cloud-worker/claude-cloud-session-process.mjs +10 -4
- package/server/claude-worker/claude-session-process.mjs +92 -15
- package/server/claude-worker/claude-turn-publisher.mjs +20 -1
- package/server/claude-worker/subagent-timeline-recorder.mjs +75 -8
- package/server/claude-worker/task-output-discovery.mjs +28 -2
- package/server/copilot-worker/copilot-ask-user-tool.mjs +1 -1
- package/server/copilot-worker/copilot-continuation-signals.mjs +11 -5
- package/server/copilot-worker/copilot-prompt-context.mjs +3 -1
- package/server/copilot-worker/copilot-question-bridge.mjs +11 -6
- package/server/copilot-worker/copilot-sdk-adapter.mjs +11 -0
- package/server/copilot-worker/copilot-sdk-session-process.mjs +24 -1
- package/server/cursor-worker/cursor-ask-user-tool.mjs +1 -1
- package/server/cursor-worker/cursor-sdk-adapter.mjs +22 -7
- package/server/cursor-worker/cursor-session-worker.mjs +9 -2
- package/server/cursor-worker/cursor-turn-runner.mjs +18 -3
- package/server/db-schema.mjs +10 -0
- package/server/grok-worker/acp-host-services.mjs +6 -3
- package/server/grok-worker/grok-background-tasks.mjs +7 -0
- package/server/grok-worker/grok-sdk-adapter.mjs +14 -0
- package/server/grok-worker/grok-turn-runner.mjs +13 -3
- package/server/migrations/0018-background-task-rows.mjs +101 -0
- package/server/public/app/action-confirmations.js +12 -15
- package/server/public/app/api-client.js +60 -4
- package/server/public/app/ask-user-view.js +29 -4
- package/server/public/app/background-tasks-view.mjs +56 -7
- package/server/public/app/bootstrap.js +7 -0
- package/server/public/app/conversation-preferences.mjs +10 -4
- package/server/public/app/conversation-provider-indicator.mjs +18 -8
- package/server/public/app/conversation-view.js +33 -9
- package/server/public/app/host-suspend-ui.js +45 -44
- package/server/public/app/journal-view.js +9 -3
- package/server/public/app/push-settings.js +1 -0
- package/server/public/app/question-answer-rule.mjs +15 -0
- package/server/public/app/question-multi-select.mjs +9 -2
- package/server/public/app/relay-event-view.mjs +5 -1
- package/server/public/app/relay-prompt-sanitizer.mjs +9 -2
- package/server/public/app/relay-restart-note-view.mjs +36 -0
- package/server/public/app/relay-restart-ui.js +568 -0
- package/server/public/app/settings-modal.js +52 -0
- package/server/public/app/socket-handlers.js +1 -1
- package/server/public/app/task-console-render.mjs +171 -7
- package/server/public/app/task-console.mjs +111 -11
- package/server/public/app/task-output-text.mjs +15 -6
- package/server/public/app/transcript-jump-bottom-view.mjs +5 -1
- package/server/public/app/transcript-jump-bottom.mjs +20 -0
- package/server/public/index.html +76 -5
- package/server/relay-exit-codes.mjs +6 -0
- package/server/relay-self-restart.mjs +6 -1
- package/server/relay-tools.md +17 -9
- package/server/repositories/message-repository.mjs +32 -28
- package/server/repositories/remote-relay-repository.mjs +4 -2
- package/server/repositories/subagent-timeline-statements.mjs +2 -0
- package/server/routes/ask-user-routes.mjs +41 -7
- package/server/routes/messages-routes.mjs +220 -37
- package/server/routes/preview-routes.mjs +73 -7
- package/server/routes/relay-board-routes.mjs +19 -2
- package/server/routes/relay-restart-routes.mjs +73 -0
- package/server/routes/sessions-routes.mjs +79 -5
- package/server/server-runtime.mjs +335 -218
- package/server/services/about-service.mjs +13 -12
- package/server/services/background-task-persistence.mjs +133 -0
- package/server/services/background-task-store.mjs +240 -13
- package/server/services/conversation-model-fallback.mjs +57 -0
- package/server/services/cursor-session-root-service.mjs +70 -6
- package/server/services/data-dir-resolution.mjs +208 -19
- package/server/services/earlier-database-service.mjs +631 -0
- package/server/services/host-suspend-command.mjs +2 -1
- package/server/services/oar-cli-helpers.mjs +6 -1
- package/server/services/oar-state-layout.mjs +107 -0
- package/server/services/oar-state-migration-service.mjs +387 -96
- package/server/services/package-data-rescue-preinstall.mjs +22 -0
- package/server/services/package-data-rescue.mjs +137 -0
- package/server/services/preview-proxy-server.mjs +14 -1
- package/server/services/preview-registry-service.mjs +3 -2
- package/server/services/process-list-service.mjs +2 -1
- package/server/services/provider-model-selection.mjs +195 -0
- package/server/services/push-dispatch-service.mjs +42 -8
- package/server/services/relay-event-service.mjs +210 -14
- package/server/services/relay-prompt-sanitizer.mjs +7 -0
- package/server/services/relay-restart-picture.mjs +209 -0
- package/server/services/relay-restart-queue.mjs +387 -0
- package/server/services/relay-shutdown-gate.mjs +4 -1
- package/server/services/relay-singleton-guard.mjs +300 -36
- package/server/services/remote-relay-caller-context.mjs +7 -2
- package/server/services/remote-relay-dispatcher.mjs +217 -28
- package/server/services/restart-resume-service.mjs +303 -0
- package/server/services/session-activity-tracker.mjs +52 -15
- package/server/services/session-worker-launch-service.mjs +31 -1
- package/server/services/session-worker-process-service.mjs +6 -4
- package/server/services/stale-turn-recovery.mjs +99 -0
- package/server/services/subagent-timeline-service.mjs +4 -1
- package/server/services/task-output-service.mjs +217 -18
- package/server/services/windows-autostart-service.mjs +329 -31
- package/server/services/windows-boot-autostart-service.mjs +188 -60
- package/shared/preview-instructions.mjs +17 -12
- package/shared/preview-tool-core.mjs +32 -14
- package/shared/question-answer-rule.mjs +63 -0
- package/shared/relay-events.mjs +106 -3
- package/shared/remote-relay-contract.mjs +32 -15
- package/shared/subagent-timeline.mjs +4 -0
- package/shared/task-output-text.mjs +196 -83
- package/shared/windows-system-tools.mjs +25 -0
- package/shared/worker-runtime/config-loader.mjs +54 -8
- package/shared/worker-runtime/prompt-context.mjs +3 -1
- package/shared/worker-runtime/worker-websocket-link.mjs +96 -20
- package/shared/worker-runtime/working-directory.mjs +65 -0
- package/server/uploads/image_input.md +0 -540
package/README.md
CHANGED
|
@@ -45,29 +45,29 @@ flowchart LR
|
|
|
45
45
|
<table>
|
|
46
46
|
<tr>
|
|
47
47
|
<td width="42%" rowspan="4" valign="top">
|
|
48
|
-
<a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.
|
|
48
|
+
<a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.14" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></picture></a>
|
|
49
49
|
<br><sub>On a phone: the same conversations, installable as an app.</sub>
|
|
50
50
|
</td>
|
|
51
51
|
<td width="58%" valign="top">
|
|
52
|
-
<a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.
|
|
52
|
+
<a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.14" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></picture></a>
|
|
53
53
|
<br><sub>Agents working in parallel, each with live token counts and its own Stop.</sub>
|
|
54
54
|
</td>
|
|
55
55
|
</tr>
|
|
56
56
|
<tr>
|
|
57
57
|
<td valign="top">
|
|
58
|
-
<a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.
|
|
58
|
+
<a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.14" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></picture></a>
|
|
59
59
|
<br><sub>When the agent needs a decision, it asks, and waits for your answer.</sub>
|
|
60
60
|
</td>
|
|
61
61
|
</tr>
|
|
62
62
|
<tr>
|
|
63
63
|
<td valign="top">
|
|
64
|
-
<a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.
|
|
64
|
+
<a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.14" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></picture></a>
|
|
65
65
|
<br><sub>Read the reasoning, not just the answer.</sub>
|
|
66
66
|
</td>
|
|
67
67
|
</tr>
|
|
68
68
|
<tr>
|
|
69
69
|
<td valign="top">
|
|
70
|
-
<a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.
|
|
70
|
+
<a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.14" alt="A session watching two other sessions: a finished reply arrives as a compact relay-event row, the watching 2 sessions list is open with Unsubscribe, and a request to also watch a session on a paired relay waits on an Allow / Deny card"></picture></a>
|
|
71
71
|
<br><sub>Agents that watch agents: woken by events, approving nothing.</sub>
|
|
72
72
|
</td>
|
|
73
73
|
</tr>
|
|
@@ -86,7 +86,7 @@ flowchart LR
|
|
|
86
86
|
|
|
87
87
|
You need Node.js 22.13 or newer (the macOS and Linux installer brings its own when yours is missing or older) and an account with the agent you want to use; see [Prerequisites](docs/install.md#prerequisites). There are also installs [with npm](docs/install.md#npm) and [from a git checkout](docs/install.md#from-a-git-checkout).
|
|
88
88
|
|
|
89
|
-
`oar` alone opens a menu. `oar start`, `stop`, `restart` and `status` control the relay, `oar service install` starts it at
|
|
89
|
+
`oar` alone opens a menu. `oar start`, `stop`, `restart` and `status` control the relay, `oar service install` starts it at boot or sign-in (a systemd user service on Linux, the sign-in autostart on Windows), `oar doctor` shows what it uses, and `oar update` installs a new release (as does **⚙️ Settings → General → Check for updates**). All the details are in [Installing and running OAR](docs/install.md): [the `oar` command](docs/install.md#the-oar-command), [where OAR keeps its files](docs/install.md#where-oar-keeps-its-files), and [updating](docs/install.md#updating).
|
|
90
90
|
|
|
91
91
|
<a id="providers"></a>
|
|
92
92
|
<a id="claude-cloud"></a>
|
|
@@ -116,11 +116,11 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
116
116
|
<table>
|
|
117
117
|
<tr>
|
|
118
118
|
<td width="62%" valign="top">
|
|
119
|
-
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.
|
|
119
|
+
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.14" alt="A running Claude turn with a message steered into it mid-turn, the reply streaming with its tool calls, and the Steer button in the composer"></picture></a>
|
|
120
120
|
<br><sub>Send a message while the agent works, and it steers the running turn.</sub>
|
|
121
121
|
</td>
|
|
122
122
|
<td width="38%" valign="top">
|
|
123
|
-
<a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.
|
|
123
|
+
<a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.14" alt="The GitHub Copilot card in Check Usage: premium requests and plan quota with reset dates, and the cost of the last turn"></picture></a>
|
|
124
124
|
<br><sub>What each plan has left, in one place.</sub>
|
|
125
125
|
</td>
|
|
126
126
|
</tr>
|
|
@@ -129,7 +129,7 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
129
129
|
- **Watch a turn live**: the reply, the agent's thoughts and every tool call appear as they happen, and each sub-agent gets a bubble of its own. **Stop** is always one tap away ([While a turn runs](docs/web-ui.md#while-a-turn-runs)).
|
|
130
130
|
- **Steer or queue**: a message sent during a turn goes into it (Claude, Copilot) or waits behind it ([While a turn runs](docs/web-ui.md#while-a-turn-runs)).
|
|
131
131
|
- **Answer questions**: the agent asks on a card with one-click choices, checkmarks or free text, and waits for you ([Question cards](docs/web-ui.md#question-cards)).
|
|
132
|
-
- **Background work**: a task panel lists the commands and sub-agents that outlive a reply, each with its own **Stop** and its full record; a command's console shows its output live, says when it has gone quiet and counts a test run's passes and failures ([Background tasks](docs/web-ui.md#background-tasks)).
|
|
132
|
+
- **Background work**: a task panel lists the commands and sub-agents that outlive a reply, each with its own **Stop** and its full record; a command's console shows its output live, says when it has gone quiet and counts a test run's passes and failures; the panel and its consoles come back after a relay restart, and a queued restart shows what it waits for and asks after a maximum wait ([Background tasks](docs/web-ui.md#background-tasks), [After a relay restart](docs/web-ui.md#after-a-relay-restart)).
|
|
133
133
|
- **Choose how the agent works**: `ask`, `plan`, `agent` or `autopilot`, plus model and reasoning effort, per message ([Relay modes](docs/web-ui.md#relay-modes)).
|
|
134
134
|
- **Files and git**: browse the workspace, preview files, review git changes in a diff viewer, and publish a dev server as a preview link ([Files, git, and previews](docs/web-ui.md#files-git-and-previews), [preview servers](docs/preview-servers.md)).
|
|
135
135
|
- **Usage and context**: plan limits and credits of every provider, and what fills the model's context window ([Usage and context](docs/web-ui.md#usage-and-context)).
|
|
@@ -137,7 +137,7 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
137
137
|
- **Share** a conversation as a read-only link, hide single messages and the host's paths from it, and see when it was opened ([Sharing](docs/web-ui.md#sharing)).
|
|
138
138
|
- **On your phone**: install OAR as an app and get push notifications ([Notifications and the app](docs/web-ui.md#notifications-and-the-app)).
|
|
139
139
|
|
|
140
|
-
The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.
|
|
140
|
+
The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.14](CHANGELOG.md#0914--2026-10-21) as the latest.
|
|
141
141
|
|
|
142
142
|
## Remote access
|
|
143
143
|
|
|
@@ -171,7 +171,7 @@ The relay keeps both tunnels running and reconnects them by itself. Beyond your
|
|
|
171
171
|
|
|
172
172
|
- **Paired relays**: connect the OAR on your workstation with the OAR on a server. An agent on one can then read, prompt and start sessions on the other, once you mention that relay with `@`.
|
|
173
173
|
- **Agent sessions**: an agent hands parts of its task to new sessions on its own relay, waits for them and reads their results. It is off until you switch it on, the first session asks for your approval, and at most 4 run at once.
|
|
174
|
-
- **Watching sessions**: an agent subscribes to other sessions and is woken when a question waits, an error happens, or a reply arrives, instead of polling.
|
|
174
|
+
- **Watching sessions**: an agent subscribes to other sessions and is woken when a question waits, an error happens, or a reply arrives, instead of polling; if it asks, also when the relay runs again after a restart.
|
|
175
175
|
|
|
176
176
|
```mermaid
|
|
177
177
|
flowchart LR
|
|
@@ -188,7 +188,7 @@ flowchart LR
|
|
|
188
188
|
|
|
189
189
|
You stay in charge: per paired relay you decide whether agents may only read, also prompt, or do everything, and in the `ask` and `plan` modes every write asks you first. A subscription only notifies; it approves nothing.
|
|
190
190
|
|
|
191
|
-
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.
|
|
191
|
+
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.14"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.14"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.14" alt="A session watching two sessions on a paired relay: a reply arrives as a relay-event row, and after a short outage the events the other relay kept arrive as one replayed row" width="720"></picture></a></p>
|
|
192
192
|
|
|
193
193
|
How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
|
|
194
194
|
|
package/bin/oar.js
CHANGED
|
@@ -11,8 +11,17 @@ import net from 'net';
|
|
|
11
11
|
|
|
12
12
|
import {
|
|
13
13
|
migrateStateToOarRoot,
|
|
14
|
-
resolveOarRoot,
|
|
15
14
|
} from '../server/services/oar-state-migration-service.mjs';
|
|
15
|
+
import { resolveDataDir, resolveUploadDir } from '../server/services/data-dir-resolution.mjs';
|
|
16
|
+
import { TAKEOVER_PROGRESS_FILE } from '../server/services/earlier-database-service.mjs';
|
|
17
|
+
import { defaultIsProcessAlive, lockHolderMayRun, processIdentity } from '../server/services/relay-singleton-guard.mjs';
|
|
18
|
+
import {
|
|
19
|
+
getLauncherLogDir,
|
|
20
|
+
getLegacyManagedConfigDir,
|
|
21
|
+
isGitCheckout,
|
|
22
|
+
resolveRelayStateDirs,
|
|
23
|
+
resolveStateLayout,
|
|
24
|
+
} from '../server/services/oar-state-layout.mjs';
|
|
16
25
|
import {
|
|
17
26
|
buildDefaultConfig, buildServicePath, buildSystemdUnit, describeRelayAddress, DOCTOR_PROBES, generateAuthToken,
|
|
18
27
|
NPM_ALLOW_SCRIPTS_ARG, parseCliArgs, primaryLanAddress, renderDoctorReport, resolveNpmInvocation, usageText,
|
|
@@ -56,20 +65,42 @@ function readRelayLock(lockPath) {
|
|
|
56
65
|
pid: Number.parseInt(String(parsed?.pid ?? ''), 10),
|
|
57
66
|
startedAt: typeof parsed?.startedAt === 'string' ? parsed.startedAt : null,
|
|
58
67
|
token: typeof parsed?.token === 'string' ? parsed.token : '',
|
|
68
|
+
format: Number(parsed?.format) || 0,
|
|
59
69
|
};
|
|
60
70
|
} catch {
|
|
61
71
|
return null;
|
|
62
72
|
}
|
|
63
73
|
}
|
|
64
74
|
|
|
75
|
+
// What a relay that holds its lock and does not answer yet is bringing over
|
|
76
|
+
// (earlier-database-service), or null: the progress file names the lock's
|
|
77
|
+
// pid, and that pid is the lock's holder still (lockHolderMayRun: a pid
|
|
78
|
+
// reused after a crash is not).
|
|
79
|
+
function readTakeover(lockPath, isProcessAliveImpl = isProcessAlive) {
|
|
80
|
+
const progress = readJsonFile(path.join(path.dirname(lockPath), TAKEOVER_PROGRESS_FILE));
|
|
81
|
+
const lock = readJsonFile(lockPath);
|
|
82
|
+
const pid = Number.parseInt(String(progress?.pid ?? ''), 10);
|
|
83
|
+
return Number.isInteger(pid) && Number(lock?.pid) === pid && lockHolderMayRun({ lock, isProcessAlive: isProcessAliveImpl }) ? progress : null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// The lock a relay that did not come up found held, and whether its holder
|
|
87
|
+
// is known to be a relay (its recorded identity matches): only when it is
|
|
88
|
+
// not may the user delete the lock. Null when no live process holds it.
|
|
89
|
+
function heldLock(lockPath, isProcessAliveImpl = isProcessAlive) {
|
|
90
|
+
const lock = readJsonFile(lockPath);
|
|
91
|
+
const pid = Number.parseInt(String(lock?.pid ?? ''), 10);
|
|
92
|
+
if (!Number.isInteger(pid) || !lockHolderMayRun({ lock, isProcessAlive: isProcessAliveImpl })) return null;
|
|
93
|
+
return { pid, path: lockPath, startedAt: lock.startedAt || 'unknown', known: !!lock.holder && !!processIdentity(pid) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function takeoverLine(takeover, logDir) {
|
|
97
|
+
const what = takeover ? ` (${takeover.from}, ${Math.max(1, Math.round(Number(takeover.bytes || 0) / 1048576))} MB)` : '';
|
|
98
|
+
return `[oar] The relay${takeover ? ` (process ${takeover.pid})` : ''} is bringing over the database it ran on until now${what} and is still running; it answers once that is done. Its log: ${path.join(String(logDir || ''), 'server.log')}`;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// As the server tells it: a process this user may not signal (EPERM) lives.
|
|
65
102
|
function isProcessAlive(pid) {
|
|
66
|
-
|
|
67
|
-
try {
|
|
68
|
-
process.kill(pid, 0);
|
|
69
|
-
return true;
|
|
70
|
-
} catch {
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
103
|
+
return defaultIsProcessAlive(pid);
|
|
73
104
|
}
|
|
74
105
|
|
|
75
106
|
function readJsonFile(filePath) {
|
|
@@ -91,45 +122,66 @@ function openAppendFileDescriptor(filePath) {
|
|
|
91
122
|
return fs.openSync(filePath, 'a');
|
|
92
123
|
}
|
|
93
124
|
|
|
94
|
-
// Pre-OAR managed config location — kept only as a migration source and as the
|
|
95
|
-
// config home for git checkouts, where nothing may change underfoot.
|
|
96
|
-
function getLegacyManagedConfigDir(env = process.env) {
|
|
97
|
-
if (process.platform === 'win32') {
|
|
98
|
-
return path.join(
|
|
99
|
-
env.LOCALAPPDATA || path.join(os.homedir(), 'AppData', 'Local'),
|
|
100
|
-
'copilot-remote',
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
return path.join(
|
|
104
|
-
env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'),
|
|
105
|
-
'copilot-remote',
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function isGitCheckout(packageRoot) {
|
|
110
|
-
try { return fs.existsSync(path.join(packageRoot, '.git')); } catch { return false; }
|
|
111
|
-
}
|
|
112
|
-
|
|
113
125
|
/**
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
*
|
|
117
|
-
*
|
|
118
|
-
*
|
|
126
|
+
* Brings pre-OAR state into a global install's state root. Without
|
|
127
|
+
* --migrate-from it runs quietly, once; with it, it runs unless that source
|
|
128
|
+
* was migrated already, never overwrites, and says what it did or why it did
|
|
129
|
+
* nothing. Database and uploads go where the relay `oar` starts will read
|
|
130
|
+
* them, resolved as the relay resolves them. Returns null to go on, or the
|
|
131
|
+
* result to stop with. A refusal stops only `oar setup` (`strict`): `oar
|
|
132
|
+
* copilot` warns and starts the session on the state root as it is.
|
|
119
133
|
*/
|
|
120
|
-
function
|
|
121
|
-
|
|
122
|
-
|
|
134
|
+
async function migrateIntoStateRoot({
|
|
135
|
+
layout, env, packageRoot, migrateFrom = '', logger, isProcessAliveImpl = isProcessAlive, strict = true,
|
|
136
|
+
}) {
|
|
137
|
+
const serverDir = path.join(packageRoot, 'server');
|
|
138
|
+
const relayEnv = { COPILOT_WEB_RELAY_DATA_DIR: resolveRelayStateDirs({ env, layout }).dataDir };
|
|
139
|
+
const dataDir = resolveDataDir({ env: relayEnv, serverDir });
|
|
140
|
+
const source = migrateFrom ? path.resolve(migrateFrom) : '';
|
|
141
|
+
let migration;
|
|
142
|
+
try {
|
|
143
|
+
migration = await migrateStateToOarRoot({
|
|
144
|
+
targetRoot: layout.root,
|
|
145
|
+
targetDataDir: dataDir,
|
|
146
|
+
targetUploadDir: resolveUploadDir({ dataDir, env: relayEnv, serverDir }),
|
|
147
|
+
repoServerDir: source ? path.join(source, 'server') : null,
|
|
148
|
+
managedConfigDir: getLegacyManagedConfigDir(env),
|
|
149
|
+
isProcessAliveImpl,
|
|
150
|
+
});
|
|
151
|
+
} catch (error) {
|
|
152
|
+
logger.error?.(`[oar] State migration failed, nothing was changed at the source: ${error?.message || error}`);
|
|
153
|
+
return { code: 1, reason: 'migration-failed', error };
|
|
154
|
+
}
|
|
155
|
+
const refused = (message, reason) => {
|
|
156
|
+
logger.error?.(`[oar] ${message}`);
|
|
157
|
+
if (strict) return { code: 1, reason };
|
|
158
|
+
logger.error?.('[oar] Nothing was migrated; starting on the state root as it is.');
|
|
159
|
+
return null;
|
|
160
|
+
};
|
|
161
|
+
switch (migration.status) {
|
|
162
|
+
case 'blocked-live-relay': return refused(migration.error, 'migration-blocked');
|
|
163
|
+
case 'refused-relay-running': return refused(migration.error, 'migration-relay-running');
|
|
164
|
+
case 'refused-existing-database': return refused(migration.error, 'migration-refused');
|
|
165
|
+
case 'nothing-to-migrate':
|
|
166
|
+
return source ? refused(`Nothing to migrate from ${source}: ${migration.reason}.`, 'migration-nothing') : null;
|
|
167
|
+
case 'already-migrated':
|
|
168
|
+
if (source) logger.log?.(`[oar] ${layout.root} is already migrated from ${source}; nothing to do.`);
|
|
169
|
+
return null;
|
|
170
|
+
case 'migrated':
|
|
171
|
+
if (migration.movedAsideTo) {
|
|
172
|
+
logger.log?.(`[oar] The state root's database had no conversations yet (a fresh one); moved it aside to ${migration.movedAsideTo}.`);
|
|
173
|
+
}
|
|
174
|
+
logger.log?.(`[oar] Migrated ${source || 'earlier relay state'} into ${layout.root} (${migration.copied} items, sources untouched; receipt in backups/MIGRATION.md).`);
|
|
175
|
+
for (const kept of migration.kept || []) {
|
|
176
|
+
logger.log?.(`[oar] Kept the existing ${kept}; the one from the source was not copied.`);
|
|
177
|
+
}
|
|
178
|
+
for (const link of migration.skipped || []) {
|
|
179
|
+
logger.log?.(`[oar] Skipped the link ${link}: what it points to was not copied.`);
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
default:
|
|
183
|
+
return null;
|
|
123
184
|
}
|
|
124
|
-
const root = resolveOarRoot(env);
|
|
125
|
-
return { checkout: false, root, configDir: root, dataDir: path.join(root, 'data') };
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
function getLauncherLogDir(env = process.env, layout = null) {
|
|
129
|
-
const envLogDir = String(env.COPILOT_WEB_RELAY_LOG_DIR || '').trim();
|
|
130
|
-
if (envLogDir) return envLogDir;
|
|
131
|
-
if (layout && !layout.checkout) return path.join(layout.root, 'logs');
|
|
132
|
-
return path.join(getLegacyManagedConfigDir(env), 'logs');
|
|
133
185
|
}
|
|
134
186
|
|
|
135
187
|
function getCopilotHomeDir(env = process.env) {
|
|
@@ -278,6 +330,7 @@ export async function launchRelay({
|
|
|
278
330
|
fetchImpl = globalThis.fetch,
|
|
279
331
|
isProcessAliveImpl = isProcessAlive,
|
|
280
332
|
lockGraceMs = 5000,
|
|
333
|
+
readyWaitMs = 20_000,
|
|
281
334
|
env = process.env,
|
|
282
335
|
logger = console,
|
|
283
336
|
} = {}) {
|
|
@@ -328,22 +381,11 @@ export async function launchRelay({
|
|
|
328
381
|
|
|
329
382
|
if (!layout.checkout) {
|
|
330
383
|
// Global install: state lives in the OAR root. Migrate pre-OAR state in
|
|
331
|
-
//
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
managedConfigDir: getLegacyManagedConfigDir(env),
|
|
337
|
-
logger,
|
|
338
|
-
});
|
|
339
|
-
if (migration.status === 'blocked-live-relay') {
|
|
340
|
-
logger.error?.(`[oar] ${migration.error}`);
|
|
341
|
-
return { code: 1, reason: 'migration-blocked' };
|
|
342
|
-
}
|
|
343
|
-
} catch (error) {
|
|
344
|
-
logger.error?.(`[oar] State migration failed, nothing was changed at the source: ${error?.message || error}`);
|
|
345
|
-
return { code: 1, reason: 'migration-failed', error };
|
|
346
|
-
}
|
|
384
|
+
// (once, or as --migrate-from asks); a live source relay blocks it.
|
|
385
|
+
const stop = await migrateIntoStateRoot({
|
|
386
|
+
layout, env, packageRoot, migrateFrom: migrateFromRoot, logger, isProcessAliveImpl, strict: false,
|
|
387
|
+
});
|
|
388
|
+
if (stop) return stop;
|
|
347
389
|
}
|
|
348
390
|
|
|
349
391
|
const lockPath = layout.checkout
|
|
@@ -372,14 +414,21 @@ export async function launchRelay({
|
|
|
372
414
|
? await probeRelayPort(probe)
|
|
373
415
|
: await probeRelayBehindLock({ ...probe, lockPath, port, isProcessAliveImpl, lockGraceMs, logger });
|
|
374
416
|
if (answer === 'taken') {
|
|
375
|
-
logger.error?.(`[oar] Port ${port} is in use by another program
|
|
417
|
+
logger.error?.(`[oar] Port ${port} is in use by another program, or by a relay with another token.`);
|
|
376
418
|
logger.error?.('[oar] Stop that program, or pick another port: oar copilot --port <port>');
|
|
377
419
|
return { code: 1, reason: 'port-taken' };
|
|
378
420
|
}
|
|
421
|
+
if (answer === 'bringing-over') {
|
|
422
|
+
logger.log(takeoverLine(readTakeover(lockPath, isProcessAliveImpl), getLauncherLogDir(env, layout)));
|
|
423
|
+
return { code: 1, reason: 'bringing-over' };
|
|
424
|
+
}
|
|
379
425
|
if (runtimeConfig.managed || !fs.existsSync(runtimeConfig.configPath)) {
|
|
380
426
|
writeJsonFile(runtimeConfig.configPath, runtimeConfig.config);
|
|
381
427
|
}
|
|
382
428
|
|
|
429
|
+
// Checkouts keep the repo-local server/data default; global installs point
|
|
430
|
+
// the server at the OAR root so updates cannot wipe the database.
|
|
431
|
+
const stateDirs = resolveRelayStateDirs({ env, layout });
|
|
383
432
|
const baseEnv = {
|
|
384
433
|
...env,
|
|
385
434
|
COPILOT_WORKSPACE_ROOT: cwd,
|
|
@@ -387,11 +436,7 @@ export async function launchRelay({
|
|
|
387
436
|
COPILOT_WEB_RELAY_SERVER_DIR: serverDir,
|
|
388
437
|
COPILOT_WEB_RELAY_CONFIG: runtimeConfig.configPath,
|
|
389
438
|
COPILOT_WEB_RELAY_LOG_DIR: getLauncherLogDir(env, layout),
|
|
390
|
-
|
|
391
|
-
// the server at the OAR root so updates cannot wipe the database.
|
|
392
|
-
...(layout.checkout ? {} : {
|
|
393
|
-
COPILOT_WEB_RELAY_DATA_DIR: String(env.COPILOT_WEB_RELAY_DATA_DIR || '').trim() || layout.dataDir,
|
|
394
|
-
}),
|
|
439
|
+
...(stateDirs.dataDir ? { COPILOT_WEB_RELAY_DATA_DIR: stateDirs.dataDir } : {}),
|
|
395
440
|
GITHUB_COPILOT_PROMPT_MODE_EXTENSIONS: String(env.GITHUB_COPILOT_PROMPT_MODE_EXTENSIONS || 'true'),
|
|
396
441
|
};
|
|
397
442
|
|
|
@@ -429,7 +474,7 @@ export async function launchRelay({
|
|
|
429
474
|
return { code: 1, reason: 'spawn-invalid-child' };
|
|
430
475
|
}
|
|
431
476
|
|
|
432
|
-
const readyDeadlineMs =
|
|
477
|
+
const readyDeadlineMs = readyWaitMs;
|
|
433
478
|
const ready = await new Promise((resolve) => {
|
|
434
479
|
const deadline = Date.now() + readyDeadlineMs;
|
|
435
480
|
const probe = async () => {
|
|
@@ -457,6 +502,14 @@ export async function launchRelay({
|
|
|
457
502
|
probe();
|
|
458
503
|
});
|
|
459
504
|
|
|
505
|
+
const takeover = ready ? null : readTakeover(lockPath, isProcessAliveImpl);
|
|
506
|
+
if (takeover) {
|
|
507
|
+
// Left running: a kill would cut the copy short for nothing.
|
|
508
|
+
logger.log(takeoverLine(takeover, logDir));
|
|
509
|
+
try { fs.closeSync(serverOutFd); } catch {}
|
|
510
|
+
try { fs.closeSync(serverErrFd); } catch {}
|
|
511
|
+
return { code: 1, reason: 'bringing-over' };
|
|
512
|
+
}
|
|
460
513
|
if (!ready) {
|
|
461
514
|
logger.error?.(`[oar] Relay did not become ready at ${statusUrl}`);
|
|
462
515
|
try {
|
|
@@ -667,7 +720,11 @@ async function probeRelayBehindLock({
|
|
|
667
720
|
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
668
721
|
answer = await probeRelayPort({ statusUrl, token, fetchImpl });
|
|
669
722
|
}
|
|
670
|
-
if (answer === 'none' &&
|
|
723
|
+
if (answer === 'none' && readTakeover(lockPath, isProcessAliveImpl)) return 'bringing-over';
|
|
724
|
+
// A lock this version wrote is left to the server, which tells a reused
|
|
725
|
+
// process id itself (lockHolderMayRun): removed here, a second relay
|
|
726
|
+
// started beside one still bringing over its database.
|
|
727
|
+
if (answer === 'none' && lockedByLiveProcess() && !readRelayLock(lockPath)?.format) {
|
|
671
728
|
// Nothing listens: the lock is left over from a relay that did not
|
|
672
729
|
// stop cleanly. The server refuses to start beside a lock whose process
|
|
673
730
|
// id is alive, so it goes first.
|
|
@@ -697,6 +754,7 @@ export async function ensureRelayRunning({
|
|
|
697
754
|
fetchImpl = globalThis.fetch,
|
|
698
755
|
isProcessAliveImpl = isProcessAlive,
|
|
699
756
|
lockGraceMs = 5000,
|
|
757
|
+
readyWaitMs = 20_000,
|
|
700
758
|
homeDir = os.homedir(),
|
|
701
759
|
platform = process.platform,
|
|
702
760
|
logger = console,
|
|
@@ -710,8 +768,9 @@ export async function ensureRelayRunning({
|
|
|
710
768
|
statusUrl, token, lockPath, port, fetchImpl, isProcessAliveImpl, lockGraceMs, logger,
|
|
711
769
|
});
|
|
712
770
|
if (answer === 'oar') return { ok: true, how: 'already', logDir };
|
|
771
|
+
if (answer === 'bringing-over') return { ok: false, how: 'bringing-over', logDir, takeover: readTakeover(lockPath, isProcessAliveImpl) };
|
|
713
772
|
if (answer === 'taken') {
|
|
714
|
-
logger.error?.(`[oar] Port ${port} is in use by another program
|
|
773
|
+
logger.error?.(`[oar] Port ${port} is in use by another program, or by a relay with another token.`);
|
|
715
774
|
logger.error?.('[oar] Stop that program, or pick another port: oar setup --port <port>');
|
|
716
775
|
return { ok: false, how: 'port-taken', logDir };
|
|
717
776
|
}
|
|
@@ -739,7 +798,7 @@ export async function ensureRelayRunning({
|
|
|
739
798
|
COPILOT_WEB_RELAY_SERVER_DIR: serverDir,
|
|
740
799
|
COPILOT_WEB_RELAY_CONFIG: configPath,
|
|
741
800
|
COPILOT_WEB_RELAY_LOG_DIR: logDir,
|
|
742
|
-
COPILOT_WEB_RELAY_DATA_DIR:
|
|
801
|
+
COPILOT_WEB_RELAY_DATA_DIR: resolveRelayStateDirs({ env, layout }).dataDir,
|
|
743
802
|
},
|
|
744
803
|
detached: true,
|
|
745
804
|
stdio: ['ignore', outFd, errFd],
|
|
@@ -753,8 +812,10 @@ export async function ensureRelayRunning({
|
|
|
753
812
|
try { fs.closeSync(outFd); } catch {}
|
|
754
813
|
try { fs.closeSync(errFd); } catch {}
|
|
755
814
|
}
|
|
756
|
-
const ok = await waitForRelay({ statusUrl, token, fetchImpl, stillStarting: () => child.exitCode === null });
|
|
757
|
-
|
|
815
|
+
const ok = await waitForRelay({ statusUrl, token, fetchImpl, deadlineMs: readyWaitMs, stillStarting: () => child.exitCode === null });
|
|
816
|
+
const takeover = ok ? null : readTakeover(lockPath, isProcessAliveImpl);
|
|
817
|
+
if (takeover) return { ok: false, how: 'bringing-over', logDir, takeover };
|
|
818
|
+
return { ok, how: 'detached', logDir, ...(ok ? {} : { lock: heldLock(lockPath, isProcessAliveImpl) }) };
|
|
758
819
|
}
|
|
759
820
|
|
|
760
821
|
/**
|
|
@@ -916,7 +977,7 @@ async function restartRelay(ctx, paths, { old, viaService }) {
|
|
|
916
977
|
logger.error('[oar] The relay came back on its earlier port and token: it was started with fixed ones (oar copilot). End that session, then run: oar start');
|
|
917
978
|
return { ok: false, reason: 'fixed-settings' };
|
|
918
979
|
}
|
|
919
|
-
logger.error(`[oar] The relay did not come back — see ${path.join(paths.logDir, 'server-err.log')}`);
|
|
980
|
+
logger.error(`[oar] The relay did not come back — see ${viaService ? 'journalctl --user -u oar' : path.join(paths.logDir, 'server-err.log')}`);
|
|
920
981
|
return { ok: false, reason: 'server-not-ready' };
|
|
921
982
|
}
|
|
922
983
|
|
|
@@ -987,6 +1048,7 @@ function startRelay(ctx, paths) {
|
|
|
987
1048
|
fetchImpl: ctx.fetchImpl,
|
|
988
1049
|
isProcessAliveImpl: ctx.isProcessAliveImpl,
|
|
989
1050
|
lockGraceMs: ctx.lockGraceMs,
|
|
1051
|
+
readyWaitMs: ctx.readyWaitMs,
|
|
990
1052
|
homeDir: ctx.homeDir,
|
|
991
1053
|
platform: ctx.platform,
|
|
992
1054
|
logger: ctx.logger,
|
|
@@ -1028,12 +1090,18 @@ function warnWindowsPort(ctx, port, windowsHolder = createWindowsPortCheck(ctx))
|
|
|
1028
1090
|
function reportRelayStart(result, logger) {
|
|
1029
1091
|
// A port held by something else was already explained.
|
|
1030
1092
|
if (result.how === 'port-taken') return;
|
|
1093
|
+
if (result.how === 'bringing-over') {
|
|
1094
|
+
logger.log(takeoverLine(result.takeover, result.logDir));
|
|
1095
|
+
return;
|
|
1096
|
+
}
|
|
1031
1097
|
if (!result.ok) {
|
|
1032
|
-
logger.error?.(`[oar] The relay did not come up — see ${path.join(result.logDir, 'server-err.log')}`);
|
|
1098
|
+
logger.error?.(`[oar] The relay did not come up — see ${result.how === 'service' ? 'journalctl --user -u oar' : path.join(result.logDir, 'server-err.log')}`);
|
|
1099
|
+
if (result.lock?.known) logger.error?.(`[oar] The lock ${result.lock.path} is held by relay process ${result.lock.pid} (started ${result.lock.startedAt}): oar stop, or end process ${result.lock.pid}.`);
|
|
1100
|
+
else if (result.lock) logger.error?.(`[oar] The lock ${result.lock.path} names process ${result.lock.pid}, which cannot be told to be a relay: if no relay is running, end process ${result.lock.pid} or delete the lock.`);
|
|
1033
1101
|
return;
|
|
1034
1102
|
}
|
|
1035
1103
|
if (result.how === 'already') {
|
|
1036
|
-
logger.log('[oar] The relay is already running.
|
|
1104
|
+
logger.log('[oar] The relay is already running. It keeps the version and settings it started with until you restart it: oar restart');
|
|
1037
1105
|
} else if (result.how === 'service') {
|
|
1038
1106
|
logger.log('[oar] The relay is running as a systemd user service (systemctl --user status oar).');
|
|
1039
1107
|
} else {
|
|
@@ -1048,7 +1116,7 @@ function requireConfig(ctx, paths) {
|
|
|
1048
1116
|
}
|
|
1049
1117
|
|
|
1050
1118
|
function reportPortTaken(ctx, paths, port) {
|
|
1051
|
-
ctx.logger.error(`[oar] Port ${port} is
|
|
1119
|
+
ctx.logger.error(`[oar] Port ${port} is in use by another program, or by a relay with another token.`);
|
|
1052
1120
|
// A relay that was started before its token was changed in the config
|
|
1053
1121
|
// cannot be asked to restart: only its lock tells that it is there.
|
|
1054
1122
|
const pid = readRelayLock(paths.lockPath)?.pid;
|
|
@@ -1077,6 +1145,7 @@ export async function runStart(options = {}) {
|
|
|
1077
1145
|
reportRelayStart(result, logger);
|
|
1078
1146
|
if (result.ok) await showAddress(ctx, paths);
|
|
1079
1147
|
warnWindowsPort(ctx, endpointOf(paths.config).port);
|
|
1148
|
+
if (result.how === 'bringing-over') return { code: 0, reason: 'bringing-over' };
|
|
1080
1149
|
return { code: result.ok ? 0 : 1, reason: result.ok ? 'started' : 'server-not-ready' };
|
|
1081
1150
|
}
|
|
1082
1151
|
|
|
@@ -1092,7 +1161,7 @@ export async function runStop(options = {}) {
|
|
|
1092
1161
|
logger.error('[oar] systemctl --user stop oar failed — see: systemctl --user status oar');
|
|
1093
1162
|
return { code: 1, reason: 'service' };
|
|
1094
1163
|
}
|
|
1095
|
-
logger.log('[oar] The relay service is stopped. It starts again at the next login; to take it out: oar service remove');
|
|
1164
|
+
logger.log('[oar] The relay service is stopped. It starts again at the next boot (without lingering: at the next login); to take it out: oar service remove');
|
|
1096
1165
|
return { code: 0, reason: 'stopped' };
|
|
1097
1166
|
}
|
|
1098
1167
|
const relay = await findRelay(ctx, paths);
|
|
@@ -1172,7 +1241,7 @@ export async function runStatus(options = {}) {
|
|
|
1172
1241
|
const access = status.access === 'lan' ? 'LAN access' : 'this machine only';
|
|
1173
1242
|
if (status.relay === 'running') logger.log(`[oar] The relay is running on port ${status.port} (${access}): ${status.url}`);
|
|
1174
1243
|
else if (status.relay === 'stopped') logger.log(`[oar] The relay is stopped (port ${status.port}, ${access}). Start it with: oar start`);
|
|
1175
|
-
else logger.log(`[oar] Port ${status.port} is
|
|
1244
|
+
else logger.log(`[oar] Port ${status.port} is in use by another program, or by a relay with another token.`);
|
|
1176
1245
|
if (status.service !== 'unavailable') {
|
|
1177
1246
|
logger.log(`[oar] Service: ${{ active: 'installed, running', installed: 'installed', none: 'not installed' }[status.service]}`);
|
|
1178
1247
|
}
|
|
@@ -1205,7 +1274,7 @@ async function runWindowsService(ctx, paths, action) {
|
|
|
1205
1274
|
return { code: 0, reason: 'service-status' };
|
|
1206
1275
|
}
|
|
1207
1276
|
if (boot) {
|
|
1208
|
-
logger.error('[oar] The relay starts at boot through a scheduled task. Change that in the web UI: Settings → Autostart.');
|
|
1277
|
+
logger.error('[oar] The relay starts at boot through a scheduled task. Change that in the web UI: Settings → General → Autostart (Windows).');
|
|
1209
1278
|
return { code: 1, reason: 'boot-task' };
|
|
1210
1279
|
}
|
|
1211
1280
|
if (action === 'install' && requireConfig(ctx, paths)) return { code: 1, reason: 'no-config' };
|
|
@@ -1261,7 +1330,7 @@ export async function runService(options = {}) {
|
|
|
1261
1330
|
// A relay started by hand holds the port and the lock: it makes way first.
|
|
1262
1331
|
if ((await runStop({ ...options, argv: [] })).reason !== 'stopped') {
|
|
1263
1332
|
runQuiet('systemctl', ['--user', 'enable', 'oar'], ctx);
|
|
1264
|
-
logger.log('[oar] The service is installed and takes over at the next login, or once the relay has stopped: oar start');
|
|
1333
|
+
logger.log('[oar] The service is installed and takes over at the next boot (without lingering: at the next login), or once the relay has stopped: oar start');
|
|
1265
1334
|
return { code: 0, reason: 'service-install' };
|
|
1266
1335
|
}
|
|
1267
1336
|
}
|
|
@@ -1310,21 +1379,13 @@ export async function runSetup(options = {}) {
|
|
|
1310
1379
|
let paths = installPaths(ctx);
|
|
1311
1380
|
const { layout } = paths;
|
|
1312
1381
|
if (!layout.checkout) {
|
|
1313
|
-
const
|
|
1314
|
-
const
|
|
1315
|
-
|
|
1316
|
-
repoServerDir: migrateFromRoot ? path.join(path.resolve(migrateFromRoot), 'server') : null,
|
|
1317
|
-
managedConfigDir: getLegacyManagedConfigDir(env),
|
|
1318
|
-
logger,
|
|
1382
|
+
const migrateFrom = String(optionValue(argv, '--migrate-from') || '').trim();
|
|
1383
|
+
const stop = await migrateIntoStateRoot({
|
|
1384
|
+
layout, env, packageRoot: ctx.packageRoot, migrateFrom, logger, isProcessAliveImpl: ctx.isProcessAliveImpl,
|
|
1319
1385
|
});
|
|
1320
|
-
if (
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
}
|
|
1324
|
-
if (migration.status === 'migrated') {
|
|
1325
|
-
logger.log(`[oar] Migrated existing relay state into ${layout.root} (sources untouched).`);
|
|
1326
|
-
paths = installPaths(ctx);
|
|
1327
|
-
}
|
|
1386
|
+
if (stop) return stop;
|
|
1387
|
+
// A migration may have brought a config over.
|
|
1388
|
+
paths = installPaths(ctx);
|
|
1328
1389
|
}
|
|
1329
1390
|
|
|
1330
1391
|
const existing = paths.config;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oar-sh/oar",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OAR — Open Agent Relay: drive your local coding agents from any browser",
|
|
6
6
|
"license": "MIT",
|
|
@@ -27,7 +27,6 @@
|
|
|
27
27
|
"!server/config.json",
|
|
28
28
|
"!server/uploads/",
|
|
29
29
|
"server/uploads/README.md",
|
|
30
|
-
"server/uploads/image_input.md",
|
|
31
30
|
"shared/",
|
|
32
31
|
"!shared/**/*.test.mjs",
|
|
33
32
|
"!shared/**/*-test-harness.mjs",
|
|
@@ -39,6 +38,7 @@
|
|
|
39
38
|
"LICENSE"
|
|
40
39
|
],
|
|
41
40
|
"scripts": {
|
|
41
|
+
"preinstall": "node server/services/package-data-rescue-preinstall.mjs",
|
|
42
42
|
"start": "node server/server.js",
|
|
43
43
|
"copilot:relay": "gh copilot -- --allow-all",
|
|
44
44
|
"test": "node --import ./scripts/node-test-keep-alive.mjs --test \"server/**/*.test.mjs\" \"shared/**/*.test.mjs\" \".github/extensions/web-relay/**/*.test.mjs\" \"scripts/**/*.test.mjs\"",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"test:e2e:raw": "node ./node_modules/@playwright/test/cli.js test --config tests/playwright.config.mjs"
|
|
51
51
|
},
|
|
52
52
|
"dependencies": {
|
|
53
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
54
|
-
"@cursor/sdk": "1.0.
|
|
55
|
-
"@github/copilot-sdk": "1.0.
|
|
53
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.304",
|
|
54
|
+
"@cursor/sdk": "1.0.38",
|
|
55
|
+
"@github/copilot-sdk": "1.0.23",
|
|
56
56
|
"better-sqlite3": "12.11.1",
|
|
57
57
|
"express": "4.22.3",
|
|
58
58
|
"qrcode-terminal": "0.12.0",
|