@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
|
@@ -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
|
-
|
|
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
|