@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.
Files changed (115) hide show
  1. package/.github/extensions/web-relay/extension.mjs +9 -0
  2. package/.github/extensions/web-relay/server-lifecycle/managed-server.mjs +15 -1
  3. package/.github/extensions/web-relay/skills/question-bridge.mjs +4 -1
  4. package/CHANGELOG.md +508 -0
  5. package/README.md +12 -12
  6. package/bin/oar.js +155 -94
  7. package/package.json +5 -5
  8. package/server/README.md +479 -157
  9. package/server/claude-cloud-worker/claude-cloud-session-process.mjs +10 -4
  10. package/server/claude-worker/claude-session-process.mjs +92 -15
  11. package/server/claude-worker/claude-turn-publisher.mjs +20 -1
  12. package/server/claude-worker/subagent-timeline-recorder.mjs +75 -8
  13. package/server/claude-worker/task-output-discovery.mjs +28 -2
  14. package/server/copilot-worker/copilot-ask-user-tool.mjs +1 -1
  15. package/server/copilot-worker/copilot-continuation-signals.mjs +11 -5
  16. package/server/copilot-worker/copilot-prompt-context.mjs +3 -1
  17. package/server/copilot-worker/copilot-question-bridge.mjs +11 -6
  18. package/server/copilot-worker/copilot-sdk-adapter.mjs +11 -0
  19. package/server/copilot-worker/copilot-sdk-session-process.mjs +24 -1
  20. package/server/cursor-worker/cursor-ask-user-tool.mjs +1 -1
  21. package/server/cursor-worker/cursor-sdk-adapter.mjs +22 -7
  22. package/server/cursor-worker/cursor-session-worker.mjs +9 -2
  23. package/server/cursor-worker/cursor-turn-runner.mjs +18 -3
  24. package/server/db-schema.mjs +10 -0
  25. package/server/grok-worker/acp-host-services.mjs +6 -3
  26. package/server/grok-worker/grok-background-tasks.mjs +7 -0
  27. package/server/grok-worker/grok-sdk-adapter.mjs +14 -0
  28. package/server/grok-worker/grok-turn-runner.mjs +13 -3
  29. package/server/migrations/0018-background-task-rows.mjs +101 -0
  30. package/server/public/app/action-confirmations.js +12 -15
  31. package/server/public/app/api-client.js +60 -4
  32. package/server/public/app/ask-user-view.js +29 -4
  33. package/server/public/app/background-tasks-view.mjs +56 -7
  34. package/server/public/app/bootstrap.js +7 -0
  35. package/server/public/app/conversation-preferences.mjs +10 -4
  36. package/server/public/app/conversation-provider-indicator.mjs +18 -8
  37. package/server/public/app/conversation-view.js +33 -9
  38. package/server/public/app/host-suspend-ui.js +45 -44
  39. package/server/public/app/journal-view.js +9 -3
  40. package/server/public/app/push-settings.js +1 -0
  41. package/server/public/app/question-answer-rule.mjs +15 -0
  42. package/server/public/app/question-multi-select.mjs +9 -2
  43. package/server/public/app/relay-event-view.mjs +5 -1
  44. package/server/public/app/relay-prompt-sanitizer.mjs +9 -2
  45. package/server/public/app/relay-restart-note-view.mjs +36 -0
  46. package/server/public/app/relay-restart-ui.js +568 -0
  47. package/server/public/app/settings-modal.js +52 -0
  48. package/server/public/app/socket-handlers.js +1 -1
  49. package/server/public/app/task-console-render.mjs +171 -7
  50. package/server/public/app/task-console.mjs +111 -11
  51. package/server/public/app/task-output-text.mjs +15 -6
  52. package/server/public/app/transcript-jump-bottom-view.mjs +5 -1
  53. package/server/public/app/transcript-jump-bottom.mjs +20 -0
  54. package/server/public/index.html +76 -5
  55. package/server/relay-exit-codes.mjs +6 -0
  56. package/server/relay-self-restart.mjs +6 -1
  57. package/server/relay-tools.md +17 -9
  58. package/server/repositories/message-repository.mjs +32 -28
  59. package/server/repositories/remote-relay-repository.mjs +4 -2
  60. package/server/repositories/subagent-timeline-statements.mjs +2 -0
  61. package/server/routes/ask-user-routes.mjs +41 -7
  62. package/server/routes/messages-routes.mjs +220 -37
  63. package/server/routes/preview-routes.mjs +73 -7
  64. package/server/routes/relay-board-routes.mjs +19 -2
  65. package/server/routes/relay-restart-routes.mjs +73 -0
  66. package/server/routes/sessions-routes.mjs +79 -5
  67. package/server/server-runtime.mjs +335 -218
  68. package/server/services/about-service.mjs +13 -12
  69. package/server/services/background-task-persistence.mjs +133 -0
  70. package/server/services/background-task-store.mjs +240 -13
  71. package/server/services/conversation-model-fallback.mjs +57 -0
  72. package/server/services/cursor-session-root-service.mjs +70 -6
  73. package/server/services/data-dir-resolution.mjs +208 -19
  74. package/server/services/earlier-database-service.mjs +631 -0
  75. package/server/services/host-suspend-command.mjs +2 -1
  76. package/server/services/oar-cli-helpers.mjs +6 -1
  77. package/server/services/oar-state-layout.mjs +107 -0
  78. package/server/services/oar-state-migration-service.mjs +387 -96
  79. package/server/services/package-data-rescue-preinstall.mjs +22 -0
  80. package/server/services/package-data-rescue.mjs +137 -0
  81. package/server/services/preview-proxy-server.mjs +14 -1
  82. package/server/services/preview-registry-service.mjs +3 -2
  83. package/server/services/process-list-service.mjs +2 -1
  84. package/server/services/provider-model-selection.mjs +195 -0
  85. package/server/services/push-dispatch-service.mjs +42 -8
  86. package/server/services/relay-event-service.mjs +210 -14
  87. package/server/services/relay-prompt-sanitizer.mjs +7 -0
  88. package/server/services/relay-restart-picture.mjs +209 -0
  89. package/server/services/relay-restart-queue.mjs +387 -0
  90. package/server/services/relay-shutdown-gate.mjs +4 -1
  91. package/server/services/relay-singleton-guard.mjs +300 -36
  92. package/server/services/remote-relay-caller-context.mjs +7 -2
  93. package/server/services/remote-relay-dispatcher.mjs +217 -28
  94. package/server/services/restart-resume-service.mjs +303 -0
  95. package/server/services/session-activity-tracker.mjs +52 -15
  96. package/server/services/session-worker-launch-service.mjs +31 -1
  97. package/server/services/session-worker-process-service.mjs +6 -4
  98. package/server/services/stale-turn-recovery.mjs +99 -0
  99. package/server/services/subagent-timeline-service.mjs +4 -1
  100. package/server/services/task-output-service.mjs +217 -18
  101. package/server/services/windows-autostart-service.mjs +329 -31
  102. package/server/services/windows-boot-autostart-service.mjs +188 -60
  103. package/shared/preview-instructions.mjs +17 -12
  104. package/shared/preview-tool-core.mjs +32 -14
  105. package/shared/question-answer-rule.mjs +63 -0
  106. package/shared/relay-events.mjs +106 -3
  107. package/shared/remote-relay-contract.mjs +32 -15
  108. package/shared/subagent-timeline.mjs +4 -0
  109. package/shared/task-output-text.mjs +196 -83
  110. package/shared/windows-system-tools.mjs +25 -0
  111. package/shared/worker-runtime/config-loader.mjs +54 -8
  112. package/shared/worker-runtime/prompt-context.mjs +3 -1
  113. package/shared/worker-runtime/worker-websocket-link.mjs +96 -20
  114. package/shared/worker-runtime/working-directory.mjs +65 -0
  115. package/server/uploads/image_input.md +0 -540
@@ -13,6 +13,7 @@ import path from "path";
13
13
  import { generateHighEntropyToken } from "./utils/token-generation.mjs";
14
14
  import { delay, killProcessTree, sleep } from "./utils/process-utils.mjs";
15
15
  import {
16
+ carryOverPackageConfig,
16
17
  resolveRelayPaths,
17
18
  loadTokenFromConfig,
18
19
  loadRelayInstructionsFromFile,
@@ -60,7 +61,13 @@ const {
60
61
  LOG_DIR,
61
62
  SERVER_LOG_PATH,
62
63
  SERVER_ERR_PATH,
64
+ RELAY_ENV,
63
65
  } = resolveRelayPaths(import.meta.url);
66
+ // A global install's config moves to the state root with the relay's data:
67
+ // the one the package held before is copied there once (never over one).
68
+ const CARRIED_OVER_CONFIG = RELAY_ENV.COPILOT_WEB_RELAY_CONFIG
69
+ ? carryOverPackageConfig({ serverDir: SERVER_DIR, configPath: CONFIG_PATH, dataDir: RELAY_ENV.COPILOT_WEB_RELAY_DATA_DIR })
70
+ : null;
64
71
  const SERVER_URL = resolveRelayServerUrl({ configPath: CONFIG_PATH });
65
72
  const SERVER_START_TIMEOUT_MS = 20_000;
66
73
  const NODE_BIN = process.env.COPILOT_WEB_RELAY_NODE || "node";
@@ -78,6 +85,7 @@ const MODEL_SNAPSHOT_MIN_INTERVAL_MS = 30_000;
78
85
  const BANNER_DEDUPE_TTL_MS = 6 * 60 * 60 * 1000;
79
86
  const BANNER_DEDUPE_COOLDOWN_MS = 15 * 1000;
80
87
  const { dbg } = createDebugLogger({ logDir: LOG_DIR });
88
+ if (CARRIED_OVER_CONFIG) dbg("copied the relay config", CARRIED_OVER_CONFIG, "to", CONFIG_PATH);
81
89
  const api = createApiClient({
82
90
  serverUrl: SERVER_URL,
83
91
  token: TOKEN,
@@ -117,6 +125,7 @@ const managedServerLifecycle = createManagedServerLifecycle({
117
125
  serverDir: SERVER_DIR,
118
126
  serverLogPath: SERVER_LOG_PATH,
119
127
  serverErrPath: SERVER_ERR_PATH,
128
+ relayEnv: RELAY_ENV,
120
129
  relayPort: Number(new URL(SERVER_URL).port) || 3333,
121
130
  nodeBin: NODE_BIN,
122
131
  serverStartTimeoutMs: SERVER_START_TIMEOUT_MS,
@@ -10,6 +10,9 @@ export function createManagedServerLifecycle({
10
10
  serverDir,
11
11
  serverLogPath,
12
12
  serverErrPath,
13
+ // The state root's folders a global install pins (resolveRelayPaths'
14
+ // RELAY_ENV); what the CLI's own environment names wins.
15
+ relayEnv = {},
13
16
  relayPort = 3333,
14
17
  nodeBin,
15
18
  serverStartTimeoutMs,
@@ -24,6 +27,9 @@ export function createManagedServerLifecycle({
24
27
  let restartTimer = null;
25
28
  let restartAttempts = 0;
26
29
  let attemptedSqliteRepair = false;
30
+ // The relay stopped on purpose (RELAY_STOP_EXIT_CODE): its reason is in
31
+ // its log, and starting it again at once cannot help.
32
+ let stoppedOnPurpose = false;
27
33
  // Keep in sync with server/relay-self-restart.mjs. Passing this on argv (not
28
34
  // in the env) is deliberate: this extension's own env is inherited from the
29
35
  // relay worker that launched the CLI, so an env flag here would be read by
@@ -33,6 +39,7 @@ export function createManagedServerLifecycle({
33
39
  const RESTART_BACKOFF_MS = [1000, 2000, 5000, 10000, 20000];
34
40
  // Keep in sync with server/relay-exit-codes.mjs
35
41
  const RELAY_RESTART_EXIT_CODE = 75;
42
+ const RELAY_STOP_EXIT_CODE = 78;
36
43
  const STARTUP_LOG_TAIL_BYTES = 24 * 1024;
37
44
  const STATUS_PATH = "/api/status";
38
45
  const RELAY_PORT = Number.isInteger(Number(relayPort)) && Number(relayPort) > 0
@@ -198,6 +205,7 @@ export function createManagedServerLifecycle({
198
205
  async function waitForServerReady(timeoutMs = serverStartTimeoutMs) {
199
206
  const deadline = Date.now() + timeoutMs;
200
207
  while (Date.now() < deadline) {
208
+ if (stoppedOnPurpose) return false;
201
209
  try {
202
210
  await api("GET", STATUS_PATH);
203
211
  return true;
@@ -280,12 +288,16 @@ export function createManagedServerLifecycle({
280
288
 
281
289
  dbg("starting managed web server", serverDir);
282
290
  managedServerOwned = true;
291
+ stoppedOnPurpose = false;
283
292
  startedOwnedProcess = true;
284
293
  const serverArgs = ["server.js", RELAY_SUPERVISED_FLAG, "--token", token];
285
294
  if (legacyOwnerPidWatchdog) {
286
295
  serverArgs.push("--owner-pid", String(process.pid));
287
296
  }
288
297
  const serverEnv = { ...process.env };
298
+ for (const [key, value] of Object.entries(relayEnv || {})) {
299
+ if (!String(serverEnv[key] || "").trim() && value) serverEnv[key] = value;
300
+ }
289
301
  if (startupWorkspaceRoot) {
290
302
  serverEnv.COPILOT_WORKSPACE_ROOT = startupWorkspaceRoot;
291
303
  }
@@ -300,13 +312,14 @@ export function createManagedServerLifecycle({
300
312
  managedServerProc.once("exit", (code, signal) => {
301
313
  const normalizedCode = Number.isInteger(Number(code)) ? Number(code) : null;
302
314
  const intentionalRestart = normalizedCode === RELAY_RESTART_EXIT_CODE;
315
+ stoppedOnPurpose = normalizedCode === RELAY_STOP_EXIT_CODE;
303
316
  dbg(
304
317
  "managed web server exited",
305
318
  `code=${code ?? "null"}`,
306
319
  `signal=${signal ?? "none"}`,
307
320
  intentionalRestart ? "intentionalRestart=true" : "intentionalRestart=false",
308
321
  );
309
- const shouldRestart = desiredRunning && managedServerOwned && !stoppingManagedServer;
322
+ const shouldRestart = desiredRunning && managedServerOwned && !stoppingManagedServer && !stoppedOnPurpose;
310
323
  closeManagedServerStreams();
311
324
  managedServerProc = null;
312
325
  managedServerOwned = false;
@@ -357,6 +370,7 @@ export function createManagedServerLifecycle({
357
370
  closeManagedServerStreams();
358
371
  managedServerProc = null;
359
372
  managedServerOwned = false;
373
+ if (stoppedOnPurpose) markNonRetryable(error);
360
374
  if (desiredRunning && !stoppingManagedServer && error?.relayRetryable !== false) {
361
375
  scheduleManagedServerRestart("start-failed");
362
376
  }
@@ -77,7 +77,10 @@ export function createQuestionBridge({
77
77
  mode: activeMsg?.relayMode || "agent",
78
78
  prompt: extractQuestionPrompt(request),
79
79
  choices,
80
- allowFreeform: allowFreeform ?? !choices.length,
80
+ // Free text unless the request says otherwise, as the runtime's ask_user
81
+ // and the SDK engine's bridge read it: the relay refuses typed text on a
82
+ // card sent with allowFreeform:false.
83
+ allowFreeform: allowFreeform ?? true,
81
84
  requestedSchema: requestedSchema || undefined,
82
85
  sdk_session_id: activeSession?.sdkSessionId || undefined,
83
86
  timeout_ms: questionTimeoutMs,
package/CHANGELOG.md CHANGED
@@ -3,6 +3,514 @@
3
3
  All notable changes to OAR are documented here. The format follows
4
4
  [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow semver.
5
5
 
6
+ ## [0.9.14] — 2026-10-21
7
+
8
+ Highlights: a relay started by Windows autostart, or by a plain
9
+ `gh copilot`, now runs on the same data folder as `oar start`, outside the
10
+ installed package, so an update no longer takes its conversations with it;
11
+ the update copies that data before npm removes the old version, and the
12
+ relay takes it over at its next start (read the upgrade note first). A
13
+ relay never moves to another database unannounced, a lock an unclean end
14
+ left no longer blocks the next start, autostart waits for a late drive, and
15
+ Cursor conversations keep their agent across updates. An agent can ask to
16
+ be woken when the relay runs again (`relay_restarted`), its preview tool
17
+ reaches only its own conversation's previews, approval cards take a choice
18
+ instead of typed text, and a conversation whose folder is gone says so.
19
+
20
+ ### Upgrade notes
21
+
22
+ - **Windows autostart users, and anyone whose relay a plain `gh copilot`
23
+ started: save your data before this update.** Before 0.9.14, those relays
24
+ ran on the database and uploads inside the installed package, and
25
+ `npm i -g` deletes them with the old version. This update copies them
26
+ first, while it installs, to `rescued-package-data/<time>/` in the OAR
27
+ data folder (`%APPDATA%\oar\data`, `~/.oar/data`), and the relay takes
28
+ the copy over at its next start. npm 10 and 11 run that copy (11.21 with
29
+ a warning), and if it fails they put the old version back with its data.
30
+ npm 12 skips it unless `@oar-sh/oar` is allowed: the install then
31
+ succeeds and the old data is gone. With `strict-allow-scripts`, npm 11
32
+ and 12 refuse the install and change nothing; `--ignore-scripts` skips
33
+ the copy everywhere, and the native modules (better-sqlite3, koffi) too,
34
+ so the relay cannot start afterwards: never update OAR with
35
+ `--ignore-scripts`. On npm 12, update with the relay stopped:
36
+ `npm i -g @oar-sh/oar --allow-scripts=@oar-sh/oar,better-sqlite3,koffi`.
37
+ An `allow-scripts` entry in `.npmrc` does not help the **Update** button
38
+ or `oar update` of 0.9.13: their own `--allow-scripts` replaces it. To be
39
+ safe, do this update by hand:
40
+ 1. Close every `gh copilot` session (its extension starts the relay
41
+ again), then `oar stop`, and wait until `oar status` says it is not
42
+ running. If 0.9.13's `oar stop` does not stop a relay the
43
+ system-startup launcher started, end the relay before installing:
44
+ end the relay's processes in Task Manager (node.exe running OAR).
45
+ 2. Run `npm root -g` and open `<that folder>/@oar-sh/oar/server`
46
+ (usually `%APPDATA%\npm\node_modules\@oar-sh\oar\server`). Copy
47
+ **both** the `data` and the `uploads` folders, with every file in
48
+ them, to a safe place.
49
+ 3. `npm i -g @oar-sh/oar --allow-scripts=@oar-sh/oar,better-sqlite3,koffi`.
50
+ 4. Before starting the relay, copy both folders back into the same
51
+ `server` folder.
52
+ 5. `oar start`. In `relay-console.log` in the OAR `logs` folder, look
53
+ for `[data] Brought over the database …`: your conversations should
54
+ be back. A database that was already in the OAR data folder is kept
55
+ in `backups/<time>/` beside it. The folder brought over keeps its
56
+ database as `copilot.db.adopted-<time>`; the others looked at keep
57
+ theirs as `copilot.db.older-<time>` (if a rescue copy was used, the
58
+ package's own database ends up `.older-`).
59
+ 6. If the relay stops with `[data] Not starting: each database has
60
+ changes newer than the other's`, both hold work; the error line names
61
+ both folders, and one may be under
62
+ `rescued-package-data/<time>/data`. Pick the one to keep, and move
63
+ the other one's `copilot.db`, `copilot.db-wal` and `copilot.db-shm`
64
+ into a folder of your own (do not delete them). Then `oar start`
65
+ again.
66
+ 7. If you already updated without saving and nothing was copied, the
67
+ database from the package can't be recovered by OAR.
68
+ - The npm package guard allows 8 MiB unpacked and 530 entries (was 7.75
69
+ MiB and 520); the take-over of earlier databases, the install-time copy
70
+ and the lock checks grew the package to about 7.75 MiB and 521 entries.
71
+
72
+ ### Added
73
+
74
+ - **Woken when the relay runs again** (`relay_restarted`, opt-in per
75
+ conversation). A conversation that queued a relay restart, or watched
76
+ sessions, slept after the restart until someone wrote, because the timers
77
+ its CLI had scheduled might not survive it. An agent can now subscribe to
78
+ `relay_restarted` through `remote_relay` (no sessions needed, no approval
79
+ card, this relay only); after each start it gets one relay event with when
80
+ the relay came back and how it stopped (who requested the restart, when
81
+ and why, or that it did not record how it stopped). Never part of the
82
+ default events. A restart requested while a turn it woke still runs does
83
+ not wake that conversation (a one-shot subscription held back so ends),
84
+ archived and deleted conversations are not woken, and each start is told
85
+ once.
86
+
87
+ ### Changed
88
+
89
+ - **Help texts and CLI messages say what the relay does.** **Accept prompts
90
+ from other relays' agents** lists everything it refuses when off, watching
91
+ sessions included; **Show Suspend host action** says it is a switch of this
92
+ browser and suspend works on Windows relays only; the Cursor session token
93
+ names the real usage bars; the Copilot engine applies whenever a
94
+ conversation's worker starts. `oar` points to **Settings → General →
95
+ Autostart (Windows)**, says the service starts again at boot with
96
+ lingering, sends a failed start or restart through the service to
97
+ `journalctl --user -u oar` instead of `server-err.log`, words a busy port
98
+ one way, and says an already running relay keeps its version and settings
99
+ until `oar restart`.
100
+ - **Agent guidance.** The ask_user example in the Copilot engines' relay
101
+ guidance is a real question instead of quiz placeholders, the guidance and
102
+ the Cursor and Copilot ask_user descriptions give the choice counts the
103
+ relay accepts (it keeps the first 8), the remote relay section names every
104
+ subscription action and `usage`, and the restart note says a queued
105
+ restart waits for every running turn and holds new messages.
106
+ - **Docs brought in line with the code**: the paired-relay switch, watch
107
+ approvals per relay, no push for agent-started turns, provider rebinding of
108
+ unstarted chats, OpenAI key removal, `oar stop`/`oar restart` under the
109
+ systemd service (they end running turns and workers), Claude turns on the
110
+ bundled Claude Code, the config keys and environment variables, which
111
+ variables reach workers started under tmux, the worker supervisor and the
112
+ database migrations.
113
+ - **Uploads follow the config's `dataDir`.** It moved the database but
114
+ left uploads in `server/uploads` unless `COPILOT_WEB_RELAY_DATA_DIR` was
115
+ set; they now go to `<dataDir>/uploads` as well. Uploads earlier versions
116
+ left in `server/uploads`, or that a state migration copied into the state
117
+ root's `uploads/`, are still read from there in place: every attachment
118
+ opens after the update, and nothing in those folders is moved or deleted.
119
+ - `@anthropic-ai/claude-agent-sdk` 0.3.304 (was 0.3.292; bundled Claude Code
120
+ 2.1.304). Claude now forks sub-agents that inherit the conversation
121
+ (`subagent_type: "fork"`) by default. With thinking turned off, a model that
122
+ cannot run without thinking now uses adaptive thinking. A full disk during a
123
+ login refresh is reported as a failed request instead of a sign-in error.
124
+ Fixes include an approved tool call that could run twice on resume, model
125
+ switches and interrupts waiting behind a context-usage check, and
126
+ `hook_progress` messages piling up in memory.
127
+ - `@cursor/sdk` 1.0.38 (was 1.0.36), which uses the relay's own `zod`
128
+ instead of carrying a copy of zod 3.
129
+ - `@github/copilot-sdk` 1.0.23 (was 1.0.17; bundled Copilot CLI 1.0.100).
130
+ The Copilot workers and the web-relay extension are unchanged: under the
131
+ Copilot CLI both run the SDK that comes with the installed CLI. **About
132
+ OAR** shows the new version.
133
+
134
+ ### Fixed
135
+
136
+ - **Windows autostart starts the relay on the same data as `oar start`.**
137
+ The sign-in entry and the system-startup launcher set only the config
138
+ file, so a relay they started kept its database, uploads and log inside
139
+ the installed package: other conversations and settings than with
140
+ `oar start`, and gone with the next update. Both now also set the data
141
+ and log folders `oar start` uses (`%APPDATA%\oar\data` and
142
+ `%APPDATA%\oar\logs` on a global install). Once it runs, the relay brings
143
+ an entry or launcher that an earlier version (0.9.0 on) generated for
144
+ this install and its config up to date, the pre-rebrand
145
+ `copilot-remote-web-relay.cmd` under its own name, so the first start
146
+ after the update (by `oar start`, `oar update` or the autostart itself)
147
+ repairs it. An entry edited by hand, one that points at another config
148
+ file, and one of a relay started on data folders of its own are left
149
+ alone: switch autostart off and on in Settings to replace those. Rewriting
150
+ a launcher while the relay it started still runs is safe: its cmd window
151
+ ends with the relay instead of starting it a second time. The launchers
152
+ now switch cmd to UTF-8, so a user folder with non-ASCII characters in its
153
+ name no longer garbles the paths in them. Conversations a relay started
154
+ by autostart kept inside the package folder are not carried over.
155
+ - **A relay never moves to another database unannounced.** Before its own
156
+ database opens, a global install's relay pinned to the OAR data folder
157
+ looks once at each database it may have run on until now (the one inside
158
+ the package, the install-time copies of it): a newer one is copied in
159
+ after a `quick_check` of the copy, with the uploads and Cursor agent
160
+ stores (never over a file there), the older one in place is kept in
161
+ `backups/<time>/`, and one line says so. Each one looked at is renamed
162
+ in place to `copilot.db.adopted-<time>` or `copilot.db.older-<time>`,
163
+ never deleted. When neither is clearly newer, or a relay may still run
164
+ on the other, the relay stops with one line naming both and exit code
165
+ 78, which its supervisors, and a systemd unit written by this version's
166
+ `oar service install`, do not retry. The swap survives a kill. A global
167
+ install always runs on the state root's data folder, however it is
168
+ started; a `dataDir` in config.json that an earlier autostart relay used
169
+ is copied from once and left exactly as it is. A git checkout and a
170
+ relay on another state root are left alone. A first start that finds an earlier launcher
171
+ but no database of it says the update may have removed it. A large
172
+ database takes minutes: the relay logs its size at the start, progress
173
+ at least once a minute and each step's time at the end, and `oar start`
174
+ meanwhile says it is still bringing it over. A second `oar start` then
175
+ no longer removes the relay's lock and starts a second relay.
176
+ - **A relay lock an unclean end left no longer blocks the next start.** A
177
+ lock from before the last boot, or whose pid now belongs to a process
178
+ started after it, is stale; a lock still held stops the start with one
179
+ line naming it and exit code 78. A lock is never seen half written, and
180
+ one that cannot be read is read again and held unless its time is more
181
+ than 10 s off (an empty one a power loss left). Two starts recovering
182
+ the same stale lock no longer both run.
183
+ - **A `TEMP` or `TMP` set with quotes, or naming a missing folder, is fixed
184
+ for the relay** on Windows (quotes off, else the user's temp folder).
185
+ - **The relay a plain `gh copilot` starts keeps its data outside the
186
+ package.** After `oar copilot` installed the Copilot CLI extension for
187
+ every folder, a plain `gh copilot` ran its relay on `server/data` and
188
+ `server/config.json` inside the installed package, on every platform. A
189
+ global install's extension now starts it on the OAR state root's
190
+ config, data and logs, as `oar start` does, and copies a config the
191
+ package held there once. The database from the package is saved only
192
+ where the install-time copy ran (see the upgrade note).
193
+ - **Windows autostart waits for a late drive.** Both the sign-in entry and
194
+ the system-startup launcher now wait up to 5 minutes in all for the
195
+ `%TEMP%` folder and the drive of the data folder (a RAM disk or a drive a
196
+ startup script mounts later), where the system-startup launcher waited
197
+ 2 minutes for `%TEMP%` only and the sign-in entry not at all. A wait that
198
+ runs out writes one line naming what is still missing, then starts the
199
+ relay anyway: `oar-web-relay-launcher.log` beside the system-startup
200
+ launcher, `oar-web-relay.log` in the OAR folder (`%APPDATA%\oar`) for the
201
+ sign-in entry. A data folder on a network share is not waited for. The
202
+ first start after the update brings a generated entry or launcher up to
203
+ date without an admin confirmation; edited ones stay as they are. A relay
204
+ whose data or upload folder cannot be created (its drive still missing, a
205
+ file in its place) now stops with one line naming the folder and exit
206
+ code 78 instead of an error trace. A `TEMP`
207
+ set with quotes (`"D:\My Temp"`) no longer stops the system-startup
208
+ launcher before it starts the relay.
209
+ - **Cursor conversations keep their agent across updates.** The Cursor
210
+ worker kept its per-conversation agent stores in
211
+ `server/data/cursor-agents` inside the installed package, so an update
212
+ deleted them and a Cursor conversation went on with a new agent that knew
213
+ nothing of the earlier turns. The stores now live in `cursor-agents` in
214
+ the relay's data folder (`~/.oar/data` on a global install, unchanged
215
+ `server/data` in a checkout); `CURSOR_AGENT_STORE_DIR` still overrides
216
+ it. The copies the state migration made there are read again, as they
217
+ were at the migration. A conversation with a store in both places uses
218
+ the one changed last, and one with a store only at the old place keeps
219
+ using it there; nothing is moved or deleted.
220
+ - **`oar setup --migrate-from` works after the installer has run.** The
221
+ state migration wrote its marker even when it found nothing to bring
222
+ over, and every later `--migrate-from <dir>` then did nothing without a
223
+ word. The marker is now written only after a real copy, and a folder
224
+ named with `--migrate-from` is always migrated, with a line saying what
225
+ was copied or why nothing was. It still never overwrites: a `config.json`
226
+ already in the state root is kept; the empty database the installer's
227
+ relay created is moved into `backups/` (the relay has to be stopped
228
+ first, `oar stop`), and a database with conversations makes it refuse
229
+ with what to do instead.
230
+ The same folder again only says it is already migrated, and a refused
231
+ `oar copilot --migrate-from` warns and starts the session anyway. While
232
+ the migration copies a database it holds the relay's lock, so no relay
233
+ starts on it in between. Migrated uploads now land in `data/uploads`,
234
+ where a global install's relay reads them (they went to an `uploads`
235
+ folder nothing read); uploads an earlier migration left in
236
+ `~/.oar/uploads` are merged into `data/uploads` at the next relay start
237
+ (nothing is overwritten or deleted). A migration that fails its check
238
+ removes only what it copied, no longer the whole state folder, and never
239
+ a link or file that was there before; a database it moved aside and
240
+ cannot put back stays in `backups/`. Symlinked folders in the checkout
241
+ are copied as real folders.
242
+ - **An agent's preview tool reaches only its own conversation's previews.**
243
+ The tool's `list` showed every live preview of the relay with its token,
244
+ the only credential of a preview, and `close` closed any of them. `list`
245
+ now shows the calling conversation's previews, and `close` of another
246
+ conversation's preview is refused with a message saying so. Copilot and
247
+ Grok sessions that use the HTTP API are taught the calls with their
248
+ conversation written in. The relay enforces this for every preview
249
+ request that names a conversation (the `conversationId` query or the
250
+ worker's conversation header); the web UI's Settings › Previews and
251
+ `/preview list` still show and close every preview. Agents present the
252
+ same relay token as the owner, so an agent that calls the API by hand
253
+ without naming a conversation still sees them all; see
254
+ docs/preview-servers.md.
255
+ - **A conversation whose working directory is gone says so.** A Claude turn
256
+ in a folder that had been deleted or renamed failed with "native binary …
257
+ exists but failed to launch", blaming the system's libc; a Grok turn said
258
+ the Grok CLI was not installed, a Copilot (SDK engine) turn showed the
259
+ spawn's `ENOENT`. Claude, Copilot, Cursor and Grok workers now check the
260
+ folder before they start their CLI or agent in it, and the turn fails with
261
+ the folder's path and what to do: restore it, or choose another one with
262
+ **⋯ → Change CWD**, then send the message again. A worker that cannot be
263
+ started at all for that reason (a launch without tmux, as on Windows)
264
+ names the folder in its launch note instead of a missing `node` or
265
+ `cmd.exe`.
266
+ - **Answering a question no longer gets a long turn requeued by Max turn
267
+ duration.** Answering one of a turn's questions restarts the max turn
268
+ duration's clock, but only when the relay picked the turns to requeue; the
269
+ requeue itself still counted from the turn's start and took every turn
270
+ past it whenever another turn was due. Such a turn was sent again
271
+ mid-answer, without the retry limit and without the page hearing of it.
272
+ The relay now requeues exactly the turns it picked, by the same rule. The
273
+ settings help and the docs now say where the limit counts from, and that
274
+ Grok also enforces it itself, counted from the start of the prompt and
275
+ without pausing for a remote relay approval card.
276
+ - **A resumed sub-agent shows its record and is listed while it runs.** When
277
+ a Claude session sent a finished background agent a new message, the task
278
+ panel showed it running, but opening it said "This agent's record could
279
+ not be loaded", and the session's task list (`read_session`,
280
+ `list_sessions`) did not list it at all: the relay took it for the
281
+ agent's first run, which had ended. The resumed agent now
282
+ opens its own record, which goes on where the first run ended (a note
283
+ marks where it resumed) and reads running again until it ends; it is a
284
+ running task of the session again, with a start and an end of its own.
285
+ - **Approval cards take a choice, not typed text, and every one reads it the
286
+ same way.** OAR's own Allow / Deny cards (agent sessions, watching
287
+ sessions, write actions in the ask and plan modes) and Claude Cloud's
288
+ permission cards showed a reply box, and whatever was typed there ("yes",
289
+ "ok", "allow it") counted as a denial; Claude Cloud even denied a typed
290
+ "allow". These cards have no reply box now, the relay refuses typed text
291
+ for any card that does not take it (the card stays open), and a choice
292
+ counts however it is typed. A card created through the API with choices
293
+ and no `allowFreeform` is such a card too (the flag defaults to `false`
294
+ when choices are given). Copilot's permission card keeps its reply box,
295
+ and now says that a typed reply refuses and tells Copilot why. A card's
296
+ header (**Agent sessions**, **Relay events**, **Remote relay**, Claude's
297
+ question headers) is shown above the question, and a question's push
298
+ notification names the provider that asks instead of always saying
299
+ "Copilot needs your input".
300
+ - **Reasoning effort reaches every Cursor model that takes one.** Cursor
301
+ models whose effort parameter is named `reasoning_effort` (Claude Haiku 5.5,
302
+ Claude Sonnet 5.5, Grok 4.7, Gemini 4 Argon, Gemini 3.8 Flash, Muse Wonder
303
+ 1.0) offered only None in the composer and could not be sent an effort.
304
+ They now list their tiers once the Cursor models are discovered again
305
+ (**Refresh** in **Select Models**, or saving the Cursor key), and the
306
+ chosen effort is sent with the turn.
307
+
308
+ ## [0.9.13] — 2026-10-20
309
+
310
+ Highlights: a queued relay restart now shows every page what it waits for,
311
+ which messages it holds and which background tasks keep running, and after a
312
+ time you set it asks instead of waiting forever; it never restarts by itself
313
+ while turns run, and can tell the sessions a restart cut off to carry on
314
+ (opt-in). The task console survives a relay restart, marks where a `| tail`
315
+ holds output back, keeps the task's row in view, and counts a test run over
316
+ its whole log. A message
317
+ sent without a model keeps the conversation's model, effort and mode,
318
+ `remote_relay` refuses a model the provider does not list, watching agents
319
+ are no longer woken by every finished shell command, workers reconnect after
320
+ a hung restart, and the unit suite passes on Windows again.
321
+
322
+ ### Upgrade notes
323
+
324
+ - Migration 0018 (the table `background_task_rows`) runs by itself on the
325
+ first start. Rows the runtime before this update held in memory are lost
326
+ one last time at the restart into it. The Node.js requirement is unchanged
327
+ (22.13 or newer).
328
+ - New settings in **Settings › General**: **Ask after a queued relay restart
329
+ has waited** (30 minutes by default) and **After a relay restart, tell
330
+ interrupted sessions to carry on** (off by default); a new push event,
331
+ **Queued relay restart waited its maximum time** (Settings › Notifications),
332
+ is on by default.
333
+ - **Watching agents:** a subscription that named no events now leaves out
334
+ successful shell commands (sub-agent ends and failures still wake it); one
335
+ that named its events keeps them. Name `task_finished` to get every task end.
336
+ - **Scripts that post `/api/message` without a model** to an existing
337
+ conversation now get the conversation's own model, effort and mode, or a
338
+ `409 CONVERSATION_MODEL_UNAVAILABLE` when that model left the catalog,
339
+ instead of the relay's first model.
340
+ - The npm package guard allows 7.75 MiB unpacked and 520 entries (was 7.5
341
+ MiB and 510); the restart queue, the stored task rows and the restart note
342
+ grew the server and the app to about 7.57 MiB and 514 entries.
343
+
344
+ ### Added
345
+
346
+ - **After a relay restart, tell interrupted sessions to carry on** (Settings
347
+ › General, off by default, a setting of the relay). A turn a restart cut
348
+ off for good (the agent had already taken the message, so it cannot run
349
+ again) used to stop without the agent ever hearing of it. With the setting
350
+ on, its session gets one compact **relay** note once the relay is back:
351
+ when the relay restarted, and to check what it was doing and carry on.
352
+ The user's message is never sent again, and nothing goes to idle sessions,
353
+ to turns the relay requeues (they run again by themselves), to archived
354
+ conversations or while a usage-limit pause holds the session; a message
355
+ queued meanwhile goes first.
356
+ - **The task console marks where a `| tail` holds the output back.** A
357
+ command piped into `tail` (`npm test 2>&1 | tail -40`) writes nothing
358
+ until it ends, so its console looked stuck. When the task's command line
359
+ shows such a pipe (`tail -f` and `head` pass lines on and are left
360
+ alone), the console marks where the output stops, with a hint to log to a
361
+ file instead, until more output comes or the task ends; the row's quiet
362
+ hint says the same on hover. The Claude, Copilot and Grok workers now report a
363
+ shell task's command line on its row (scrubbed of credentials by the relay
364
+ and cut at 2,000 characters; `read_session` passes it on).
365
+ - **Load earlier in the console's header.** It sat only above the first
366
+ line, out of reach while following the end of a long log; the header now
367
+ has it too whenever there is earlier output.
368
+
369
+ - **A queued relay restart shows what it waits for, and asks instead of
370
+ waiting forever.** **🌄 Restart web relay** first lists the sessions with
371
+ a running turn it would wait for, the messages it would hold, and the
372
+ sessions with background tasks (commands, monitors, sub-agents), each with
373
+ whether it keeps running; **Restart when idle** queues the restart,
374
+ **Restart now** does not wait. While a restart is queued every page shows a
375
+ banner: *Relay restart queued — waiting for N sessions*, the sessions as
376
+ links, how long it has waited, that new messages are held until then, the
377
+ background tasks, and **Restart now** (it asks first and names the
378
+ sessions it does not wait for) and **Cancel restart**. A new setting,
379
+ **Ask after a queued relay restart has waited** (Settings › General,
380
+ 30 minutes by default, 5 minutes to 4 hours or no limit), bounds the wait:
381
+ after it the banner asks *restart now, keep waiting or cancel?* and a push
382
+ notification (a new event, on by default) asks the same. It never
383
+ restarts while the turns run, and still restarts as soon as they end;
384
+ **Keep waiting** waits another period.
385
+ `POST /api/relay/shutdown` answers with the same picture (`waitingFor`,
386
+ `held`, `backgroundTasks` with `survives`), so an agent that requests a
387
+ restart can tell the user, and takes `immediate: true`; `GET
388
+ /api/relay/shutdown` and `POST /api/relay/shutdown/keep-waiting` are new.
389
+
390
+ ### Changed
391
+
392
+ - **The open console leaves the task's row in view.** On a screen wider
393
+ than a phone the console now ends above the task's row in the composer
394
+ panel, so the row's ✓/✘ chip, clock and **Stop** stay visible and live
395
+ beside it; on a phone it stays full-screen, with the same chip in its
396
+ header.
397
+ - **One wording for a task without output.** The row's hover title said
398
+ "output not available for this provider" and the console "Output is not
399
+ available for this provider."; both now use the sentence, from the same
400
+ text the relay answers with.
401
+ - **A watching agent is no longer woken for every shell command that ended
402
+ well.** A relay event subscription that names no `events` now gets
403
+ `subagent_finished` instead of `task_finished`: a background sub-agent that
404
+ finished still wakes the watcher, and so does any task that failed or
405
+ exited non-zero (`task_failed`), but a shell command that ended well does
406
+ not. A busy worker ends one every minute or so, and each was a turn of the
407
+ watcher on its whole context (35 during one 20-minute wait were seen).
408
+ Name `task_finished` in `events` to get every task end, as before.
409
+ Existing subscriptions that named no events switch to the new default by
410
+ themselves; those that named their events keep them. A paired relay on an
411
+ older OAR applies its own default and refuses `subagent_finished` by name.
412
+
413
+ ### Fixed
414
+
415
+ - **A message sent without a model keeps the conversation's model and
416
+ effort.** A message to an existing Copilot conversation that named no model
417
+ (sent by a script or an agent, not by the composer) ran on the first model
418
+ of the catalog with effort `none`, which some models refuse, and then saved
419
+ that model and effort as the conversation's own, so the composer showed
420
+ them from then on. Such a message now runs on the conversation's model and
421
+ effort, and saves nothing it did not name. If the conversation's model has
422
+ left the catalog, the send is refused with a message saying so instead of
423
+ switching to another model. OpenAI, Claude, Cursor and Grok conversations
424
+ keep their effort the same way, and so does the follow-up of a plan
425
+ board's action. A message that names no mode runs in the conversation's
426
+ mode instead of agent mode, and no longer switches a plan-mode
427
+ conversation to agent. A message whose turn failed can be sent again at
428
+ once: it no longer counts as a duplicate of itself.
429
+ - **Test counts of a long run no longer drop out.** The relay counted a
430
+ test run from the last 64 KB of its output only, so a live run that wrote
431
+ more lost its runner's first line: the chip disappeared or undercounted.
432
+ A run still going is now counted over its whole log, read on as it grows
433
+ (and read again in bounded steps after a relay restart); the closing
434
+ summary still decides once it is there.
435
+ - **Credentials in background-task rows are blanked.** A task's
436
+ description, summary and last tool call went to the pages, `read_session`
437
+ and the stored console rows as the worker sent them, and a Grok terminal's
438
+ (or a Copilot shell's) description is its command line. The relay now
439
+ scrubs them like the console output. The scrubber also catches `curl -u
440
+ name:password` (also attached), `--password`/`--token`/`--secret`
441
+ values, `mysql -p…`, `docker login -p`, `sshpass -p`, `redis-cli -a`,
442
+ `aws_secret_access_key` in a file or `aws configure set`, Slack webhook
443
+ paths, `?key=`/`&sig=`/`&token=` query values and an `Authorization`
444
+ header in any case and with any scheme word, and leaves a reference such
445
+ as `$(cat ~/.token)` or `$API_TOKEN` alone.
446
+ - **Inverse text in the task console.** vitest's RUN, PASS and FAIL badges
447
+ (SGR 7) showed as plain words; inverse text now swaps its colours, so
448
+ they show filled.
449
+ - **The task console survives a relay restart.** Its rows lived in the
450
+ relay's memory: after a restart a running command came back to the panel
451
+ once its worker reported, but the dimmed rows of commands that had
452
+ finished within the hour were gone, and a console open at the time said
453
+ the output was no longer available. The rows are now kept in the database
454
+ (migration 0018): finished rows come back with their ✓ or ✘ for the rest
455
+ of their hour, and a running command's console reads its output at once.
456
+ Such a command is shown as running again only when its worker says so
457
+ (until then its console says it waits for the worker, without **Stop**);
458
+ one whose worker did not survive the restart is marked finished with an
459
+ unknown result (a neutral ? instead of ✓ or ✘) after about a minute and a
460
+ half, its output still readable.
461
+ - **A scroll right after older messages load is no longer undone.** The page
462
+ put the reader back where they were when the older page arrived, a frame
463
+ later: scrolling in that frame (up again after the jump-to-bottom button,
464
+ for instance) was reverted, and could leave you at the end, following new
465
+ rows you meant to read later.
466
+ - **The jump-to-bottom badge counts a message that arrives just as you scroll
467
+ up.** One that landed before the page had noted the scroll, a frame and on
468
+ a busy page up to a tenth of a second, was taken as already seen.
469
+ - **The background-task panel no longer shrinks back for a moment.** While
470
+ the agent works the page re-reads the conversation, and an answer read
471
+ before the panel grew put the smaller set of tasks back until the next
472
+ read. The panel jumped, and a reader following the newest messages could be
473
+ left above them.
474
+ - **A worker reconnects after a relay restart even when its connect hangs.**
475
+ A worker's link to the relay was retried only once the socket reported an
476
+ error or a close. A connect that never finished, a socket the relay
477
+ accepted but never greeted, or a close that never completed while the
478
+ relay hung in its shutdown left the worker without a connection and
479
+ without a next attempt, and messages to its session stayed queued; the
480
+ relay's replacement of such a worker does not step in while the session
481
+ is working. The link now gives up a socket that has not completed its
482
+ handshake within 15 seconds, does not wait for a close after the relay
483
+ says it is shutting down, and retries a socket that fails to construct.
484
+ There is always exactly one next attempt, with the usual backoff of up to
485
+ 8 seconds, which now starts over only after a completed handshake. Failed
486
+ attempts log one line each in the worker log, at most one a minute after
487
+ the first three.
488
+ - **`remote_relay` refuses a model the provider does not list.**
489
+ `create_session` passed any model id on, so a Copilot id written with a
490
+ hyphen ("gpt-5-4" for "gpt-5.4") created a session on another model, which
491
+ every later `send` with that id failed to reach. A model in
492
+ `create_session` or `send` is now checked against the list New Chat offers
493
+ for the provider on the target relay, before any approval card: an unknown
494
+ one is refused with the close matches and the valid ids ("Unknown model
495
+ 'gpt-5-4' for Copilot on relay …; did you mean 'gpt-5.4'?"), and nothing is created
496
+ or sent. Claude also takes `opus`, `sonnet` and `haiku` (the newest listed
497
+ model of that name), and a Copilot id with its effort (`gpt-5.4-high`) runs
498
+ as that model with that effort. When the relay has no list for the provider yet, the
499
+ model goes on unchecked and the answer says so. A relay also refuses an
500
+ unknown Copilot model when another relay's agent starts a session on it.
501
+ - **A preview's websocket is dropped when the browser hangs up before the dev
502
+ server answers.** A browser that closed its connection with a plain FIN
503
+ while the upgrade was still waiting on the dev server left both sides
504
+ open, even when a reset followed: the request
505
+ to the dev server stayed pending, and the preview lane could not stop
506
+ until the dev server answered. Both sides are now dropped, and stopping
507
+ the lane also drops upgrades still waiting on the dev server.
508
+ - **The unit test suite passes and finishes on Windows again.** A preview
509
+ test waited forever for a websocket hang-up (fixed in the product, above),
510
+ and several tests expected bare program names or Unix paths where Windows
511
+ resolves the full path of its own tools; two unlocks recorded in the same
512
+ millisecond are now listed in the order they were recorded.
513
+
6
514
  ## [0.9.12] — 2026-10-18
7
515
 
8
516
  Highlights: a background command or monitor in the task panel opens a