@oar-sh/oar 0.9.19 → 0.9.21
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/polling/polling-loop.mjs +2 -26
- package/.github/extensions/web-relay/skills/question-routing-hooks.mjs +2 -26
- package/CHANGELOG.md +529 -0
- package/README.md +14 -14
- package/bin/oar.js +392 -98
- package/package.json +4 -2
- package/server/claude-cloud-worker/claude-cloud-event-normalizer.mjs +60 -0
- package/server/claude-cloud-worker/claude-cloud-session-process.mjs +258 -1
- package/server/claude-cloud-worker/claude-cloud-session-worker.mjs +22 -1
- package/server/claude-cloud-worker/claude-cloud-worker-steering.mjs +34 -0
- package/server/claude-worker/claude-remote-relay-tool.mjs +1 -0
- package/server/claude-worker/claude-session-process.mjs +10 -0
- package/server/claude-worker/claude-session-worker-main.mjs +8 -1
- package/server/claude-worker/claude-turn-publisher.mjs +163 -8
- package/server/claude-worker/claude-usage-totals.mjs +58 -0
- package/server/copilot-worker/copilot-plan-board.mjs +4 -7
- package/server/copilot-worker/copilot-sdk-event-normalizer.mjs +86 -3
- package/server/copilot-worker/copilot-sdk-session-process.mjs +131 -5
- package/server/copilot-worker/copilot-sdk-session-worker.mjs +8 -1
- package/server/cursor-worker/cursor-session-worker.mjs +12 -1
- package/server/cursor-worker/cursor-turn-runner.mjs +42 -6
- package/server/cursor-worker/cursor-worker-steering.mjs +20 -0
- package/server/db-schema.mjs +18 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +14 -0
- package/server/grok-worker/grok-background-tasks.mjs +9 -0
- package/server/grok-worker/grok-session-worker.mjs +12 -1
- package/server/grok-worker/grok-turn-runner.mjs +47 -6
- package/server/grok-worker/grok-worker-steering.mjs +23 -0
- package/server/migrations/0001-add-structured-answer.mjs +5 -7
- package/server/migrations/0002-recent-workspace-roots-path-key.mjs +5 -7
- package/server/migrations/0003-push-subscriptions.mjs +5 -7
- package/server/migrations/0004-steer-settle-markers.mjs +5 -7
- package/server/migrations/0005-remote-relays.mjs +5 -7
- package/server/migrations/0006-workspace-root-settings.mjs +5 -7
- package/server/migrations/0007-claude-cloud.mjs +5 -7
- package/server/migrations/0008-message-pins.mjs +5 -7
- package/server/migrations/0009-subagent-timeline.mjs +5 -7
- package/server/migrations/0010-subagent-timeline-pruning.mjs +5 -7
- package/server/migrations/0011-relay-event-subscriptions.mjs +5 -7
- package/server/migrations/0012-cross-relay-event-subscriptions.mjs +5 -7
- package/server/migrations/0013-previews.mjs +5 -7
- package/server/migrations/0014-conversation-groups.mjs +5 -7
- package/server/migrations/0015-conversation-group-position.mjs +5 -7
- package/server/migrations/0016-share-access-log.mjs +5 -7
- package/server/migrations/0017-conversation-history-indexes.mjs +5 -7
- package/server/migrations/0018-background-task-rows.mjs +5 -7
- package/server/migrations/0019-relay-event-subscription-options.mjs +5 -7
- package/server/migrations/0020-conversation-may-create-sessions.mjs +5 -7
- package/server/migrations/0021-conversation-group-agent-marks.mjs +5 -7
- package/server/migrations/0022-relay-schedules.mjs +5 -7
- package/server/migrations/0023-relay-asks.mjs +5 -7
- package/server/migrations/0024-deepseek-usage-totals.mjs +5 -7
- package/server/migrations/0025-deepseek-usage-requests-daily.mjs +5 -7
- package/server/migrations/0026-deepseek-usage-served-models.mjs +5 -7
- package/server/migrations/0027-deepseek-usage-checkpoint-runs.mjs +5 -7
- package/server/migrations/0028-deepseek-usage-checkpoint-request-runs.mjs +5 -7
- package/server/migrations/0029-deepseek-balance-rises.mjs +5 -7
- package/server/migrations/0030-deepseek-usage-actuals.mjs +5 -7
- package/server/migrations/0031-queue-provider.mjs +5 -7
- package/server/migrations/0032-runtime-session-billing-switch.mjs +5 -7
- package/server/migrations/0033-claude-api-usage-checkpoints.mjs +5 -7
- package/server/migrations/0034-claude-api-usage-totals.mjs +5 -7
- package/server/migrations/0035-conversation-plan-only.mjs +87 -0
- package/server/migrations/0036-token-usage.mjs +267 -0
- package/server/public/app/about-ui.js +1 -1
- package/server/public/app/about-view.mjs +106 -3
- package/server/public/app/action-confirmations.js +1 -1
- package/server/public/app/api-client.js +6 -3
- package/server/public/app/ask-user-view.js +20 -6
- package/server/public/app/attachments-view.js +1 -1
- package/server/public/app/back-guard.mjs +31 -5
- package/server/public/app/bootstrap.js +611 -70
- package/server/public/app/chat-actions-menu-state.mjs +139 -4
- package/server/public/app/chat-header-meta.mjs +27 -0
- package/server/public/app/claude-billing-toggle-ui.js +2 -1
- package/server/public/app/claude-cloud-conversation-ui.js +2 -2
- package/server/public/app/composer-control-state.mjs +12 -2
- package/server/public/app/composer-settings-label.mjs +90 -0
- package/server/public/app/composer-settings-ui.js +442 -0
- package/server/public/app/context-menu.mjs +36 -5
- package/server/public/app/context-usage-view.mjs +16 -0
- package/server/public/app/conversation-deep-link.mjs +15 -11
- package/server/public/app/conversation-group-drag.mjs +12 -2
- package/server/public/app/conversation-group-order.mjs +9 -2
- package/server/public/app/conversation-group-state.mjs +47 -10
- package/server/public/app/conversation-origin-ui.js +17 -1
- package/server/public/app/conversation-view.js +20 -2
- package/server/public/app/cwd-menu-placement.mjs +18 -6
- package/server/public/app/cwd-picker.js +36 -6
- package/server/public/app/emoji-view.js +1 -1
- package/server/public/app/font-scaling.js +26 -0
- package/server/public/app/header-priority-actions.mjs +234 -0
- package/server/public/app/hidden-messages-view.mjs +4 -0
- package/server/public/app/inbox-ui.js +50 -2
- package/server/public/app/inbox-view.mjs +141 -5
- package/server/public/app/journal-view.js +311 -58
- package/server/public/app/phone-landscape-ui.js +225 -0
- package/server/public/app/phone-landscape.mjs +99 -0
- package/server/public/app/phone-typing-state.mjs +104 -0
- package/server/public/app/pinned-messages-view.mjs +5 -0
- package/server/public/app/plan-board-shared.mjs +49 -0
- package/server/public/app/plan-usage-view.mjs +1 -1
- package/server/public/app/push-settings.js +2 -2
- package/server/public/app/pwa-install.js +24 -3
- package/server/public/app/relay-board-inline.mjs +4 -1
- package/server/public/app/relay-board-view.js +38 -10
- package/server/public/app/relay-event-subscriptions-ui.js +71 -22
- package/server/public/app/relay-event-view.mjs +42 -0
- package/server/public/app/relay-restart-ui.js +2 -2
- package/server/public/app/relay-schedule-view.mjs +32 -6
- package/server/public/app/relay-schedules-ui.js +69 -20
- package/server/public/app/remote-relay-origin-view.mjs +41 -23
- package/server/public/app/session-history-ui.js +250 -0
- package/server/public/app/session-history.mjs +599 -0
- package/server/public/app/settings-modal.js +61 -6
- package/server/public/app/settings-tabs.js +12 -0
- package/server/public/app/share-state.js +1 -1
- package/server/public/app/shared-conversations-settings.js +2 -2
- package/server/public/app/socket-handlers.js +15 -4
- package/server/public/app/status-view.mjs +18 -2
- package/server/public/app/store.js +87 -16
- package/server/public/app/subagent-viewer.mjs +27 -2
- package/server/public/app/tab-strip-fade.mjs +101 -0
- package/server/public/app/tap-actions.js +66 -0
- package/server/public/app/usage-limit-ui.js +63 -2
- package/server/public/app/viewer-history.mjs +13 -2
- package/server/public/index.html +2258 -1585
- package/server/public/sw.js +8 -0
- package/server/relay-heap-guard.mjs +14 -1
- package/server/relay-self-restart.mjs +83 -2
- package/server/relay-tools.md +5 -3
- package/server/repositories/message-repository.mjs +54 -1
- package/server/repositories/remote-relay-repository.mjs +10 -0
- package/server/repositories/session-repository.mjs +25 -1
- package/server/routes/claude-cloud-routes.mjs +19 -0
- package/server/routes/messages-routes.mjs +144 -1
- package/server/routes/relay-ask-routes.mjs +138 -8
- package/server/routes/relay-board-routes.mjs +55 -40
- package/server/routes/sessions-routes.mjs +144 -14
- package/server/server-runtime.mjs +322 -51
- package/server/services/about-service.mjs +81 -53
- package/server/services/cache-rebuild-service.mjs +7 -0
- package/server/services/claude-billing-swap-service.mjs +7 -20
- package/server/services/claude-cloud-session-service.mjs +47 -5
- package/server/services/conversation-preferences-service.mjs +31 -0
- package/server/services/database-file.mjs +413 -0
- package/server/services/db-backup-service.mjs +809 -0
- package/server/services/deepseek-usage-import.mjs +6 -9
- package/server/services/delete-archive-service.mjs +3 -0
- package/server/services/earlier-database-service.mjs +69 -126
- package/server/services/oar-cli-helpers.mjs +214 -12
- package/server/services/oar-cli-menu.mjs +1 -0
- package/server/services/oar-state-migration-service.mjs +33 -13
- package/server/services/package-data-rescue.mjs +4 -2
- package/server/services/plan-usage-claude-api.mjs +14 -15
- package/server/services/plan-usage-contract.mjs +6 -2
- package/server/services/plan-usage-deepseek.mjs +1 -1
- package/server/services/plan-usage-tokens.mjs +248 -0
- package/server/services/push-dispatch-service.mjs +134 -9
- package/server/services/relay-cli-launcher-service.mjs +3 -0
- package/server/services/relay-event-service.mjs +30 -0
- package/server/services/relay-question-context.mjs +11 -0
- package/server/services/relay-restart-picture.mjs +67 -22
- package/server/services/remote-inbox-watch-service.mjs +204 -0
- package/server/services/remote-relay-caller-context.mjs +80 -7
- package/server/services/remote-relay-dispatcher.mjs +101 -19
- package/server/services/restart-resume-service.mjs +5 -2
- package/server/services/session-activity-tracker.mjs +1 -1
- package/server/services/session-launch-mutex-service.mjs +23 -0
- package/server/services/session-worker-launch-service.mjs +12 -3
- package/server/services/session-worker-registry-service.mjs +36 -0
- package/server/services/session-worker-stop-service.mjs +27 -6
- package/server/services/session-worker-websocket-service.mjs +40 -16
- package/server/services/token-usage-service.mjs +430 -0
- package/server/services/update-install-service.mjs +13 -0
- package/server/services/usage-limit-pause-service.mjs +8 -1
- package/server/services/worker-recycle-service.mjs +772 -0
- package/server/services/worker-replacement.mjs +197 -0
- package/server/services/worker-scope.mjs +77 -0
- package/shared/build-id.mjs +128 -0
- package/shared/plan-board-actions.mjs +130 -0
- package/shared/plan-only.mjs +43 -0
- package/shared/relay-asks.mjs +3 -0
- package/shared/relay-events.mjs +38 -4
- package/shared/remote-relay-contract.mjs +66 -11
- package/shared/turn-token-usage.mjs +144 -0
- package/shared/worker-runtime/heartbeat.mjs +5 -0
- package/shared/worker-runtime/simple-steering-state.mjs +38 -0
- package/shared/worker-runtime/worker-websocket-link.mjs +23 -0
- package/shared/worker-runtime/working-directory.mjs +1 -1
|
@@ -8,6 +8,7 @@ import { getActiveSession } from "../runtime/session-registry.mjs";
|
|
|
8
8
|
import { DEFAULT_QUESTION_TIMEOUT_MS } from "../../../../shared/question-timeout.mjs";
|
|
9
9
|
import { waitForRelayQuestion } from "../../../../shared/question-wait.mjs";
|
|
10
10
|
import { EMPTY_TURN_COMPLETION_NOTE } from "../../../../shared/empty-turn-completion.mjs";
|
|
11
|
+
import { planBoardActionsFromCli } from "../../../../shared/plan-board-actions.mjs";
|
|
11
12
|
import { stripPromptContextPrefix } from "../skills/prompt-context.mjs";
|
|
12
13
|
import {
|
|
13
14
|
isOpenAIImageTerminalError,
|
|
@@ -319,34 +320,9 @@ function extractToolCallInputObject(value, toolName, depth = 0) {
|
|
|
319
320
|
}
|
|
320
321
|
|
|
321
322
|
function normalizePlanBoardActions(rawActions, recommendedAction = "") {
|
|
322
|
-
const ids = Array.isArray(rawActions)
|
|
323
|
-
? rawActions
|
|
324
|
-
.map((entry) => {
|
|
325
|
-
if (typeof entry === "string") return entry.trim().toLowerCase();
|
|
326
|
-
if (entry && typeof entry === "object") {
|
|
327
|
-
return String(entry.id || entry.actionId || entry.value || "").trim().toLowerCase();
|
|
328
|
-
}
|
|
329
|
-
return "";
|
|
330
|
-
})
|
|
331
|
-
.filter(Boolean)
|
|
332
|
-
: [];
|
|
333
|
-
const sourceIds = ids.length ? ids : ["autopilot", "interactive", "exit_only"];
|
|
334
|
-
const seen = new Set();
|
|
335
|
-
const deduped = [];
|
|
336
|
-
for (const id of sourceIds) {
|
|
337
|
-
if (seen.has(id)) continue;
|
|
338
|
-
seen.add(id);
|
|
339
|
-
deduped.push(id);
|
|
340
|
-
}
|
|
341
323
|
const recommended = String(recommendedAction || "").trim().toLowerCase();
|
|
342
324
|
return {
|
|
343
|
-
actions:
|
|
344
|
-
if (id === "autopilot_fleet") return { id, label: "Implement with autopilot fleet", mode: "autopilot" };
|
|
345
|
-
if (id === "autopilot") return { id, label: "Implement in autopilot", mode: "autopilot" };
|
|
346
|
-
if (id === "interactive") return { id, label: "Stop here and prompt myself", mode: "agent" };
|
|
347
|
-
if (id === "exit_only") return { id, label: "Stop here", mode: "agent" };
|
|
348
|
-
return { id, label: id.replace(/[_-]+/g, " "), mode: null };
|
|
349
|
-
}),
|
|
325
|
+
actions: planBoardActionsFromCli(rawActions),
|
|
350
326
|
recommendedAction: recommended || null,
|
|
351
327
|
};
|
|
352
328
|
}
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
flatAnswerToStructured,
|
|
7
7
|
summarizeStructuredAnswer,
|
|
8
8
|
} from "../../../../shared/question-schema.mjs";
|
|
9
|
+
import { planBoardActionsFromCli } from "../../../../shared/plan-board-actions.mjs";
|
|
9
10
|
|
|
10
11
|
function extractSubagentRunIdFromRequest(request) {
|
|
11
12
|
const agentId = request?.agentId || request?.data?.agentId || null;
|
|
@@ -57,34 +58,9 @@ export function createQuestionRoutingHooks({
|
|
|
57
58
|
const allowToolUse = { permissionDecision: "allow" };
|
|
58
59
|
|
|
59
60
|
function normalizePlanBoardActions(rawActions, recommendedAction = "") {
|
|
60
|
-
const ids = Array.isArray(rawActions)
|
|
61
|
-
? rawActions
|
|
62
|
-
.map((entry) => {
|
|
63
|
-
if (typeof entry === "string") return entry.trim().toLowerCase();
|
|
64
|
-
if (entry && typeof entry === "object") {
|
|
65
|
-
return String(entry.id || entry.actionId || entry.value || "").trim().toLowerCase();
|
|
66
|
-
}
|
|
67
|
-
return "";
|
|
68
|
-
})
|
|
69
|
-
.filter(Boolean)
|
|
70
|
-
: [];
|
|
71
|
-
const sourceIds = ids.length ? ids : ["autopilot", "interactive", "exit_only"];
|
|
72
|
-
const seen = new Set();
|
|
73
|
-
const deduped = [];
|
|
74
|
-
for (const id of sourceIds) {
|
|
75
|
-
if (seen.has(id)) continue;
|
|
76
|
-
seen.add(id);
|
|
77
|
-
deduped.push(id);
|
|
78
|
-
}
|
|
79
61
|
const recommended = String(recommendedAction || "").trim().toLowerCase();
|
|
80
62
|
return {
|
|
81
|
-
actions:
|
|
82
|
-
if (id === "autopilot_fleet") return { id, label: "Implement with autopilot fleet", mode: "autopilot" };
|
|
83
|
-
if (id === "autopilot") return { id, label: "Implement in autopilot", mode: "autopilot" };
|
|
84
|
-
if (id === "interactive") return { id, label: "Stop here and prompt myself", mode: "agent" };
|
|
85
|
-
if (id === "exit_only") return { id, label: "Stop here", mode: "agent" };
|
|
86
|
-
return { id, label: id.replace(/[_-]+/g, " "), mode: null };
|
|
87
|
-
}),
|
|
63
|
+
actions: planBoardActionsFromCli(rawActions),
|
|
88
64
|
recommendedAction: recommended || null,
|
|
89
65
|
};
|
|
90
66
|
}
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,535 @@
|
|
|
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.21] — 2026-11-01
|
|
7
|
+
|
|
8
|
+
Highlights: the chat screen is more compact. The mode, model, effort,
|
|
9
|
+
context window and billing of a chat move behind **⚙** in a slim label row
|
|
10
|
+
above the message box, such as `🔒 Opus 5.5 · high · Auto`. The header menu
|
|
11
|
+
is now **︙**, and the header's icon buttons stay there while there is room
|
|
12
|
+
and move into it when there is not. Spacing is about halved across the app,
|
|
13
|
+
with controls and hit areas at 28 px. Back and forward walk through the
|
|
14
|
+
conversations you opened, with `?conv=<id>` in the address bar, and back
|
|
15
|
+
closes what is open on top first. A pinch, Ctrl/⌘+wheel or Text scaling
|
|
16
|
+
zooms the whole app, not only the text. A phone held sideways keeps the
|
|
17
|
+
phone's layout. A session group folds while one of its chats is open, the
|
|
18
|
+
filter row folds or unfolds all groups at once, and a group's name takes
|
|
19
|
+
its sessions' colour. The origin, watched and scheduled pills of a sidebar
|
|
20
|
+
row become one **ⓘ**. After a deploy, the relay replaces each session worker
|
|
21
|
+
still on the old code once its session is idle, so a restart no longer
|
|
22
|
+
leaves old worker code running.
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- **Chat settings behind ⚙ in the composer.** A slim label row above the
|
|
27
|
+
message box holds **⚙**, a label that says what the next message runs
|
|
28
|
+
with (model · effort · mode, such as `🔒 Opus 5.5 · high · Auto`, with the
|
|
29
|
+
context window added when it is not the model's default), the billing pill
|
|
30
|
+
of a Claude chat, the file browser, **📎** and, on a desktop, **🙂**. **⚙**,
|
|
31
|
+
or a tap on the label, opens the settings: **Mode**, **Model** with the
|
|
32
|
+
lock note under it, **Effort**, **Context window**, **Billing** and
|
|
33
|
+
**🧠 Context usage**. A tap on one part of the label opens them at that
|
|
34
|
+
field, and the **Subscription** or **API key** pill at **Billing**. On a
|
|
35
|
+
desktop they open as a popover above the row, on a phone as a sheet from
|
|
36
|
+
the bottom; **✕**, Escape, a tap outside or back closes them. An image
|
|
37
|
+
chat shows **Quality** and **Image size** in place of Effort and Context
|
|
38
|
+
window; a Claude Cloud chat has only **Model**. The label reads **Auto**
|
|
39
|
+
for the autopilot mode (the settings keep the full name) and **Plan 🔒**
|
|
40
|
+
on a plan-only session.
|
|
41
|
+
- **Back and forward through conversations.** Each conversation you open is
|
|
42
|
+
one step in the browser's history, with `?conv=<id>` in the address bar,
|
|
43
|
+
so back and forward walk through them, and a reload or a copied address
|
|
44
|
+
opens that conversation. The tab's title is the conversation's title.
|
|
45
|
+
Back first closes what is open on top: the session list on a phone, the
|
|
46
|
+
Inbox or Status view, the composer's settings, the **︙** menu, a row's
|
|
47
|
+
menu, dialogs, the git, tmux and annotate views, the watch and schedule
|
|
48
|
+
panels, the emoji picker and the Change CWD folder list. On a phone, back
|
|
49
|
+
from the first conversation you opened shows the session list, then the
|
|
50
|
+
installed app's *Press back again to close the app* as before.
|
|
51
|
+
- A link to a conversation the relay does not know (deleted, or a wrong
|
|
52
|
+
id) clears the view with a notice and keeps the last conversation; back
|
|
53
|
+
and forward step over it from then on, and nothing opens by itself until
|
|
54
|
+
you pick a conversation. A link to an archived conversation shows its
|
|
55
|
+
title.
|
|
56
|
+
- No address is written before the first conversation shows, so a link
|
|
57
|
+
survives an early tap. A jump from the Inbox to a question replaces the
|
|
58
|
+
history entry instead of adding one.
|
|
59
|
+
- A notification tap still opens `?push_conv=<id>`; the address then
|
|
60
|
+
reads `?conv=<id>`.
|
|
61
|
+
- **Zoom scales the whole app.** A pinch, Ctrl+wheel (⌘+wheel on a Mac) or
|
|
62
|
+
**⚙️ Settings → General → Text scaling** now zooms text, buttons,
|
|
63
|
+
spacing, the sidebar, menus and dialogs alike, so the layout keeps its
|
|
64
|
+
proportions; at 100 % nothing changes. Popovers and sheets are placed
|
|
65
|
+
anew on a change. The new-chat dialog's selects and inputs follow the
|
|
66
|
+
zoom too (they stayed at the browser's fixed 13.33 px).
|
|
67
|
+
- **A phone held sideways keeps the phone's layout.** The header is one
|
|
68
|
+
line. While the keyboard is up, the composer's label row folds into a
|
|
69
|
+
floating **⚙** that opens the settings and keeps the keyboard up, and the
|
|
70
|
+
background-task line into a floating **🤖**; both come back when the
|
|
71
|
+
keyboard goes down. Picking a conversation closes the session list and
|
|
72
|
+
does not bring up the keyboard (a long press does not pick). The **︙**
|
|
73
|
+
menu gains entries for the watch list, the schedules and the origin.
|
|
74
|
+
- **Fold a group with its chat open, and fold all.** A group folds even
|
|
75
|
+
while one of its sessions is open; that session stays under the folded
|
|
76
|
+
head until you open another one, and opening a member of a folded group
|
|
77
|
+
leaves it folded. A drop on that kept row is a drop on the folded head.
|
|
78
|
+
While the list has groups, the filter row has a fold-all button: **➖**
|
|
79
|
+
folds every group, **➕** unfolds them all once all are folded. It counts
|
|
80
|
+
as your own choice for each group and is disabled while the list is
|
|
81
|
+
filtered. The Archived view keeps its folds apart from the live list.
|
|
82
|
+
- **A group's name takes its sessions' colour:** red while one waits for
|
|
83
|
+
you, bright green while one works, dark green while one is idle with its
|
|
84
|
+
session ready, else the plain title colour. Archived heads stay muted.
|
|
85
|
+
- **ⓘ on sidebar rows.** The origin, watched and scheduled pills of a row
|
|
86
|
+
fold into one **ⓘ** in its second line. Its menu lists each with its
|
|
87
|
+
action: *Started by an agent in "…"* (or *on relay "…"*), which opens
|
|
88
|
+
that conversation or the session on the other relay, *Watched by …* with
|
|
89
|
+
the watchers' names, and *Scheduled · next …* (or *N schedules · next
|
|
90
|
+
…*). A member under the head row of the session that started it leaves
|
|
91
|
+
the origin out.
|
|
92
|
+
- **Session workers are recycled after a deploy.** Every worker states the
|
|
93
|
+
build id (version and commit) it started on; a worker on another build than
|
|
94
|
+
the relay, or one too old to say, is stopped and relaunched by the relay
|
|
95
|
+
once it is idle, the way **Change CWD → 🚀 Set new CWD and (re)launch**
|
|
96
|
+
does, so a restart no longer leaves old worker code running. An archived conversation's worker is
|
|
97
|
+
stopped for good. `/api/status` and the About modal show which build each
|
|
98
|
+
worker runs.
|
|
99
|
+
- Idle means no running turn, open question card, compaction, background
|
|
100
|
+
task or message on its way in, and the worker's CLI has ended. A CLI
|
|
101
|
+
ends by itself about 10 minutes after the last delivery or turn, so a
|
|
102
|
+
session is usually replaced some 10 minutes after its last turn. A
|
|
103
|
+
worker too old to say whether its CLI runs is replaced after 10 minutes
|
|
104
|
+
without a delivery or turn end. At most two are replaced at a time, one
|
|
105
|
+
start every 3 seconds, and each session once per relay build.
|
|
106
|
+
- Nothing is lost: the new worker resumes the native session, and a
|
|
107
|
+
message sent meanwhile waits and goes to the new worker. A Cursor
|
|
108
|
+
session is the exception for background shells and tasks, which its SDK
|
|
109
|
+
does not report: only the 10-minute quiet window protects them.
|
|
110
|
+
- **About OAR** shows *Workers: 14, all on this build* or *Workers: 14,
|
|
111
|
+
3 on an older build*. `/api/status` gives the relay's `buildId` and,
|
|
112
|
+
per worker, `buildId`, `startedAt` and `stale`, and the recycle's
|
|
113
|
+
`staleCount`, replacements and the sessions it waits on with the reason.
|
|
114
|
+
relay_tool's session items carry `worker { kind, buildId, stale }`.
|
|
115
|
+
The relay log has `[worker-recycle]` lines for each replacement and,
|
|
116
|
+
at most once a minute per session, what it waits for.
|
|
117
|
+
- A Claude Cloud worker opens its event stream from the stored cursor at
|
|
118
|
+
start, so turns the cloud session starts by itself are followed after a
|
|
119
|
+
replacement too. New worker route `GET /api/claude-cloud-session`.
|
|
120
|
+
- The troubleshooting guide has a section, *Workers after a deploy*.
|
|
121
|
+
|
|
122
|
+
### Changed
|
|
123
|
+
|
|
124
|
+
- **The header menu is ︙, and the icon buttons move into it when the
|
|
125
|
+
header is narrow.** **📍** pins and **🙈** hidden messages (while there
|
|
126
|
+
are any), **🔍** search, **🧠** context usage, **📊** Check Usage,
|
|
127
|
+
**☀️/🌙** Day/Night and **⛶** fullscreen stay in the header while there is
|
|
128
|
+
room and move into **︙** under their full names otherwise, the last ones
|
|
129
|
+
first. The menu stays inside the window, is capped in height and scrolls.
|
|
130
|
+
Fullscreen is disabled where the browser has none. Relay refusals, log
|
|
131
|
+
lines and help texts that named the `⋯` menu now name **︙**.
|
|
132
|
+
- **The open-question banner sits between the title and the header
|
|
133
|
+
buttons,** so **︙** always stays at the right edge. Where room is short
|
|
134
|
+
(a phone held sideways, a narrow desktop window) it shrinks to a **❓ N**
|
|
135
|
+
chip; on a wider header it takes at most 40 % of the row and ends in "…".
|
|
136
|
+
A long title ends in "…" too. When even the chip, **☰** and **︙** do not
|
|
137
|
+
fit next to the title, as at 150 % zoom with the sidebar open, the banner
|
|
138
|
+
takes a row of its own; on a phone held upright it always does.
|
|
139
|
+
- **About half the spacing, and 28 px controls.** Paddings, margins and
|
|
140
|
+
gaps in menus, the sidebar, dialogs, Settings, cards, bubbles and panels
|
|
141
|
+
are about halved, and controls that were 30 to 46 px high are 28 px, also
|
|
142
|
+
dialog buttons and tabs. Every hit area is at least 28 px at 100 %: the
|
|
143
|
+
row's **ⓘ**, fold, archive and delete, the live reply's **Stop** and
|
|
144
|
+
**💭 📃 🛠️** follow buttons, the Previews tab's **Open**, **Copy link**
|
|
145
|
+
and **Close**, the phone's header chips and the
|
|
146
|
+
question banner, the sidebar head buttons, Settings fields, selects,
|
|
147
|
+
checkboxes, radios and sliders, the repo browser's toolbar, the group
|
|
148
|
+
picker, the dialogs' **×** and **↻**, the Relays **Add**, the Sharing
|
|
149
|
+
row buttons, **Unsubscribe** and **Cancel**. Small labels and links get
|
|
150
|
+
an invisible 28 px hit area: the repo browser's Focus and crumbs, tree
|
|
151
|
+
rows, Sharing row titles, DeepSeek **Advanced**, the plan-only label, the
|
|
152
|
+
waiting pill, the agent-exchange and activity folds and the header chips.
|
|
153
|
+
- **On a phone the lines under the title are one row** that scrolls
|
|
154
|
+
sideways, with short labels (`↗ title`, `👁 3`, `⏰ 04:15`, the folder's
|
|
155
|
+
last name), and the open-question banner gets a row of its own.
|
|
156
|
+
- **Dialogs on a phone keep their title** with a compact **×**; the header
|
|
157
|
+
buttons no longer stretch across the width. Select Models puts its three
|
|
158
|
+
buttons under the title.
|
|
159
|
+
- **A group's head.** One chevron **›** that turns down while the group is
|
|
160
|
+
open replaces ▾/▸; focus returns to it only after a keyboard toggle, with
|
|
161
|
+
a focus ring in the accent colour. A head line without a head row reads
|
|
162
|
+
"<last active> · N sessions", and its pill shows only what waits, runs or
|
|
163
|
+
failed. A long head drops its message count whole, then its date, instead
|
|
164
|
+
of cutting them. A mouse click on **ⓘ** hands focus back without a ring.
|
|
165
|
+
- **Sidebar rows.** The title and the "1 open" pill share one line and only
|
|
166
|
+
the title is cut short; the title and meta line keep clear of the archive
|
|
167
|
+
and delete buttons. Archive and delete show on keyboard focus too. The
|
|
168
|
+
filter row's archive button reads **🗄️**, and the buttons close up to the
|
|
169
|
+
right when **×** or **➕** is hidden. The sidebar head follows the zoom and
|
|
170
|
+
wraps instead of cutting off **Inbox** or **Install**.
|
|
171
|
+
- **The Claude usage warning above the composer is one line,** such as
|
|
172
|
+
`⚠ Claude 94 % weekly · resets Tue 04:00`; a tap shows the full sentence.
|
|
173
|
+
- **A sub-agent's Stop is red and asks first** (**⏹ Stop agent**).
|
|
174
|
+
- **On a phone a background task's title has its own full-width line.**
|
|
175
|
+
- **Images in messages have no card around them.**
|
|
176
|
+
- **Editing a chat's title on a desktop takes the whole header row** while
|
|
177
|
+
you type; the title lines, the banner and the header buttons come back
|
|
178
|
+
when you are done.
|
|
179
|
+
- **Settings takes the focus when it opens, and Escape closes it.** The
|
|
180
|
+
Settings and Plan usage tab strips fade at an edge while more tabs lie
|
|
181
|
+
beyond it, and keep the selected tab in view. The Features help wraps
|
|
182
|
+
long identifiers.
|
|
183
|
+
- **Pack guard ceilings.** The npm tarball may hold 610 entries and 9.5 MiB
|
|
184
|
+
unpacked (590 and 9 MiB before); 0.9.20 had used 99.3 % of the bytes.
|
|
185
|
+
|
|
186
|
+
### Fixed
|
|
187
|
+
|
|
188
|
+
- **The archive button on a sidebar row is visible on touch screens.** It
|
|
189
|
+
was invisible there but could still be tapped.
|
|
190
|
+
- **A ︙ entry tapped on a phone no longer closes what it opened.** The tap's
|
|
191
|
+
follow-up click landed on the menu's backdrop or the new dialog and closed
|
|
192
|
+
it again: About, Kill session, Check Usage, Context, Restart relay and
|
|
193
|
+
Search, and the watch and schedule panels; Git changes did not open a
|
|
194
|
+
diff.
|
|
195
|
+
- **Contrast in the light theme.** Primary buttons read at 5.19:1, red
|
|
196
|
+
buttons at 4.67:1, the sidebar's "1 open" pill at 6.47:1, Settings help
|
|
197
|
+
links at 6.85:1, the offline banner at 5.6:1 and the **☰** status tones
|
|
198
|
+
at 4.9:1 or more. A hovered accent button gets a darker fill (6.36:1)
|
|
199
|
+
instead of turning paler.
|
|
200
|
+
- **Status and the Inbox, closed with nothing to go back to,** show their
|
|
201
|
+
empty state instead of a blank pane.
|
|
202
|
+
- **The OpenAI, Grok and Cursor fields in Settings look like the others,**
|
|
203
|
+
and **Use a different token** and **Empty queue** are real buttons.
|
|
204
|
+
- **The ⚙ popover fits its width under zoom,** the full origin title shows,
|
|
205
|
+
and focus is kept when the list or header is drawn anew.
|
|
206
|
+
|
|
207
|
+
### Upgrade notes
|
|
208
|
+
|
|
209
|
+
- **Every session worker is replaced once after the update.** Workers from
|
|
210
|
+
0.9.20 state no build id, so 30 seconds after the relay first hears of
|
|
211
|
+
one it counts as on an older build and is replaced once its session is
|
|
212
|
+
idle and its CLI has ended, usually some 10 minutes after its last turn.
|
|
213
|
+
About shows *M on an older build* until then. Nothing needs to be done.
|
|
214
|
+
Each replacement costs one cold start on the session's next message.
|
|
215
|
+
- **No migration and no new dependency.** Going back to 0.9.20 needs no
|
|
216
|
+
database step.
|
|
217
|
+
|
|
218
|
+
## [0.9.20] — 2026-10-29
|
|
219
|
+
|
|
220
|
+
Highlights: every plan card gets an "Approve plan" button that approves the
|
|
221
|
+
plan without starting the implementation and keeps the session in plan mode,
|
|
222
|
+
and a session can be locked in plan mode (`planOnly`) so agents cannot take
|
|
223
|
+
it out; plan cards are in the Inbox. Check Usage shows the tokens of every
|
|
224
|
+
turn for every provider: input, output, cache writes and cache reads, for
|
|
225
|
+
today, this month and all time, and by model. The relay backs up its database
|
|
226
|
+
before a new version's migrations and keeps the last two copies; `oar backup`
|
|
227
|
+
takes one by hand. A new database is `oar.db`; an existing install keeps
|
|
228
|
+
running on `copilot.db`, nothing is renamed. Under the systemd service a
|
|
229
|
+
restart hands the relay to systemd with its new code, waits for running turns
|
|
230
|
+
and keeps session workers, and `oar service` works in a git checkout. A paired
|
|
231
|
+
relay's approval cards in the Inbox say what they are about, a card a turn
|
|
232
|
+
there waits on pushes once to your devices, and the badge is current within
|
|
233
|
+
about 20 s. An open fold in the Inbox stays open.
|
|
234
|
+
|
|
235
|
+
### Added
|
|
236
|
+
|
|
237
|
+
- **"Approve plan" on every plan card.** It is the first button of every
|
|
238
|
+
provider's card (the Copilot CLI's own list gets it too), also on cards that
|
|
239
|
+
were waiting when the relay was updated. It marks the plan approved and
|
|
240
|
+
queues exactly "Plan approved by the user. Do not implement. Finish only the
|
|
241
|
+
plan-only steps you were given, then stop." That one turn runs in agent mode
|
|
242
|
+
(plan mode blocks writes, and the plan doc may need a commit); the session
|
|
243
|
+
stays in plan mode, so the next message plans again. Pressed while the plan
|
|
244
|
+
turn still runs, the follow-up waits for it and runs as its own turn.
|
|
245
|
+
- **Plan-only sessions (`planOnly`).** An optional lock, set only when asked:
|
|
246
|
+
relay_tool `create_session` with `planOnly: true` (no mode named: it starts
|
|
247
|
+
in plan; another mode is refused), `update_session` with `planOnly: true`
|
|
248
|
+
(with `mode: "plan"` unless the session is in plan already), or the owner's
|
|
249
|
+
switch "Plan only: agents can't take this session out of plan mode" in the
|
|
250
|
+
context modal, for every session. Plan mode alone never sets it.
|
|
251
|
+
- An agent's `send` or `update_session` that names another mode gets HTTP
|
|
252
|
+
409, code `plan_only`: "This session is plan-only; agents can't change its
|
|
253
|
+
mode. Clear planOnly first, or ask the user." Nothing is queued or changed
|
|
254
|
+
(an update_session dry run refuses before any Allow card). A send without
|
|
255
|
+
a mode runs in the session's saved mode. An agent's mode change through the preferences route
|
|
256
|
+
is refused (409), and so is any planOnly change there (403).
|
|
257
|
+
- `update_session` with `planOnly: false` counts only in the same call that
|
|
258
|
+
names a mode other than plan (`{planOnly: false, mode: "agent"}`). Alone,
|
|
259
|
+
or with `mode: "plan"`, it is ignored, and on a plan-only session the answer says
|
|
260
|
+
"planOnly unchanged: clear it in the same call as the new mode", so a client that
|
|
261
|
+
sends every property as false cannot drop the lock. The owner's switch
|
|
262
|
+
clears it any time.
|
|
263
|
+
- The owner still changes the mode freely; the mode select shows "Plan 🔒".
|
|
264
|
+
On such a session the card recommends "Approve plan" and asks "This
|
|
265
|
+
session is plan-only. <label> leaves plan mode. Go on?" before "Implement
|
|
266
|
+
in autopilot", "Implement with autopilot fleet" and "Stop here and prompt
|
|
267
|
+
myself".
|
|
268
|
+
- list_sessions and read_session show `planOnly: true`. A compaction keeps
|
|
269
|
+
the lock; a turn cut by a usage limit or a restart goes on in plan, except
|
|
270
|
+
the "Approve plan" turn itself. A plan-only conversation's own agent counts
|
|
271
|
+
as a plan-mode caller during that turn too.
|
|
272
|
+
- Migration 0035 adds `conversations.plan_only`.
|
|
273
|
+
- A relay on 0.9.19 still sends its caller's mode with every `send`: a send
|
|
274
|
+
from a non-plan 0.9.19 caller to a plan-only session is refused. A
|
|
275
|
+
`create_session` with planOnly to a 0.9.19 relay creates the session
|
|
276
|
+
without the lock and says so (`planOnlyWarning`).
|
|
277
|
+
- **Relay event `board_settled`.** A session's subscribers hear when one of
|
|
278
|
+
its plan cards is settled, "Stop here" included, with `boardId`,
|
|
279
|
+
`boardType`, `action`, `actionLabel`, `title` and `queuedMessageId`. The
|
|
280
|
+
line reads:
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
[relay event] session "<title>" (<id>): board_settled "<card title>" → "<label>" (action <action>, board_id <board id>, queued message <message id>)
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
For "Stop here" it ends `(action exit_only, board_id <board id>, nothing
|
|
287
|
+
queued)`. It is one of the default event types, also for subscriptions
|
|
288
|
+
stored with the 0.9.19 default; `match` applies to its action, label and
|
|
289
|
+
card title.
|
|
290
|
+
- **Plan cards in the Inbox.** For each session that is neither deleted nor
|
|
291
|
+
archived, its newest pending plan card, with the plan folded and the card's
|
|
292
|
+
own buttons (and the plan-only question); plan cards settled in the last
|
|
293
|
+
day show what was chosen. They count in the badge. A paired relay's Inbox
|
|
294
|
+
shows their title only, with "Open there".
|
|
295
|
+
- **Other relays' approval cards are easier to notice.** A paired relay's
|
|
296
|
+
approval card in the Inbox says what it is about: "New session · the
|
|
297
|
+
session waits", then "relay B · New session requested by 'X': title,
|
|
298
|
+
model" (or loop guard, write, session settings, watch, start sessions),
|
|
299
|
+
still with "Open there" only. The agent's prompt, answer or question never
|
|
300
|
+
leaves its relay. Every relay now checks its paired relays about every
|
|
301
|
+
20 s, even with no browser open, so the badge is current within about
|
|
302
|
+
20 s. A card a turn there waits on (an approval, or a question the turn is
|
|
303
|
+
blocked on) pushes once in total: its own relay pushes as before, and when
|
|
304
|
+
that reached none of your devices, the first paired relay with push on
|
|
305
|
+
that checks it pushes instead (`Approval waiting on relay "B"`), with no
|
|
306
|
+
answer buttons; the tap opens the session there. It follows the device's
|
|
307
|
+
"questions" toggle and notification settings: with previews off, an
|
|
308
|
+
approval's body carries no summary, only its kind ("New session · the
|
|
309
|
+
session waits"); the title names the session only with the device's
|
|
310
|
+
include-title setting on.
|
|
311
|
+
- `GET /api/relay-asks/remote/inbox`: approval items gain `variant` and
|
|
312
|
+
`summary`; with `?push=1` (the caller can push), pending approval and
|
|
313
|
+
ask_user items gain `notice` (`here|wait|you|other`), and `you` gains
|
|
314
|
+
`claimed` (`now|before`). A 0.9.19 relay ignores both and never pushes
|
|
315
|
+
another relay's card. New socket event `inbox_remote_changed`. A push's
|
|
316
|
+
data may carry `url`. Claims are in memory; no migration.
|
|
317
|
+
- **The relay backs up its database before a new version's migrations.**
|
|
318
|
+
At the first start of each new version (in a git checkout: of each new
|
|
319
|
+
commit) it copies the database into `backups/` in the state root
|
|
320
|
+
(`~/.oar/backups`, `%APPDATA%\oar\backups`; `server/backups/` in a
|
|
321
|
+
checkout) before the migrations run, as one checked file named after the
|
|
322
|
+
version that ran on the data, such as
|
|
323
|
+
`oar-0.9.19-20261103-091500-pre-update.db`. It keeps the last two
|
|
324
|
+
(`dbBackups.keep` in `config.json`; `dbBackups.enabled: false` turns it off,
|
|
325
|
+
`dbBackups.dir` moves the folder) and deletes only files it wrote itself:
|
|
326
|
+
hand-made backups, `MIGRATION.md` and the `backups/<time>/` folders of
|
|
327
|
+
earlier moves stay as they are, and `oar doctor` lists them as not rotated.
|
|
328
|
+
When the disk would keep less than max(1 GiB, 5 %) free, or the folder holds
|
|
329
|
+
a `DO_NOT_TOUCH` file, the copy is skipped with a warning in the log, in
|
|
330
|
+
About, in `oar doctor` and in the in-app update's log, and the start goes on.
|
|
331
|
+
Plain restarts take no copy. While it runs, `oar start` says the relay is
|
|
332
|
+
backing up its database. A start while `oar backup` writes a copy waits
|
|
333
|
+
for it (up to 15 minutes; `oar start` says so and leaves it running), then
|
|
334
|
+
makes its own in any case.
|
|
335
|
+
- **`oar backup [--reason <text>]`.** Copies the database while the relay
|
|
336
|
+
runs, into the same folder with the same name and rotation
|
|
337
|
+
(`oar-<version>-<time>-manual.db`, or the reason given).
|
|
338
|
+
- **About and `oar doctor` show the database file** by its full path with its
|
|
339
|
+
size, and the backups: the last one, how many are kept, and the folder.
|
|
340
|
+
|
|
341
|
+
- **Tokens per turn for every provider, in Check Usage.** Every tab gets a
|
|
342
|
+
**Tokens** section (today, this month, all time) and **Tokens by model,
|
|
343
|
+
this month**, with fresh input, output, cache writes and cache reads apart
|
|
344
|
+
and the share of input the cache served. A figure a provider does not report
|
|
345
|
+
reads "—" with the reason in the section's note; 0 means reported zero.
|
|
346
|
+
Copilot turns on your own OpenAI key show as their own group, "Tokens — own
|
|
347
|
+
OpenAI key". **About OAR** shows each provider's tokens of all time,
|
|
348
|
+
Claude, Claude API and DeepSeek included. Each turn's tokens are kept per
|
|
349
|
+
run and model with the chat and go when it is deleted; the daily sums stay.
|
|
350
|
+
The Claude API and DeepSeek days from before the upgrade are copied in once,
|
|
351
|
+
with cache writes inside input. Both sections open expanded. Two model ids
|
|
352
|
+
with one name (a dated and an undated Claude id) are one row, their tokens
|
|
353
|
+
added up; pointing at the row shows the ids.
|
|
354
|
+
- Detail rows in `GET /api/usage` may carry a `title`: on a merged Tokens
|
|
355
|
+
by model row, `Model ids: <id>, <id>`. Those rows are sorted by name.
|
|
356
|
+
- New worker route `POST /api/turn-token-usage` and tables `turn_token_usage`
|
|
357
|
+
and `token_usage_days` (migration 0036). Every worker posts after each
|
|
358
|
+
turn: Claude, Claude API, DeepSeek, Claude cloud, Copilot (plan and own
|
|
359
|
+
key), Cursor and Grok.
|
|
360
|
+
- `OAR_DEBUG_TOKEN_USAGE=1` makes the Copilot worker log each model call's
|
|
361
|
+
raw token counts (no ids).
|
|
362
|
+
|
|
363
|
+
### Changed
|
|
364
|
+
|
|
365
|
+
- **No plan summary in a card's follow-up.** "Implement in autopilot",
|
|
366
|
+
"Implement with autopilot fleet" and "Stop here and prompt myself" queue
|
|
367
|
+
only their line; the plan is no longer repeated after it.
|
|
368
|
+
- **relay_tool `send` without `mode` leaves the target's mode alone.** It
|
|
369
|
+
used to run the target's turn in the calling session's mode (a plan-mode
|
|
370
|
+
agent put the sessions it prompted into plan mode). Name `mode` when a turn
|
|
371
|
+
should run in a given one. `create_session` without a mode still takes the
|
|
372
|
+
calling session's.
|
|
373
|
+
- **Switching between plan mode and a writing mode during a running turn
|
|
374
|
+
queues the message instead of steering it in.** A steer switched the running
|
|
375
|
+
turn to the message's mode, so a plan turn could gain write access halfway
|
|
376
|
+
through, or a writing turn lose it. Such a message now waits and runs as its
|
|
377
|
+
own turn once the current one ends; the composer reads "Queue" for it.
|
|
378
|
+
Messages in a mode on the same side (agent, ask and autopilot with each
|
|
379
|
+
other, or plan with plan) steer in as before.
|
|
380
|
+
- **A new database is `oar.db`.** The relay's database file gets the
|
|
381
|
+
product's name: a database created from nothing is `oar.db` (with
|
|
382
|
+
`oar.db-wal` and `oar.db-shm`). An existing install keeps running on
|
|
383
|
+
`copilot.db`; nothing is renamed, and `oar doctor` and About show which file
|
|
384
|
+
a relay runs on. A database brought over from elsewhere (an earlier
|
|
385
|
+
install's package, `oar setup --migrate-from`) keeps its own name.
|
|
386
|
+
- **`oar.db` and `copilot.db` in one data folder.** When
|
|
387
|
+
`oar.db` and `copilot.db` are both there, the start reads both. A
|
|
388
|
+
`copilot.db` that is not newer, is empty, or holds only what a start of an
|
|
389
|
+
earlier version writes (no conversation, message or other work; a few
|
|
390
|
+
start-up settings) is set aside as
|
|
391
|
+
`copilot.db.older-<time>`, never deleted, and the relay runs on `oar.db`.
|
|
392
|
+
A `copilot.db` with newer work, or each newer somewhere, stops the start
|
|
393
|
+
(exit code 78) with both files named and nothing changed: move the one not
|
|
394
|
+
to go on with into a folder of your own, then start again. An `oar.db-wal`
|
|
395
|
+
or `oar.db-shm` without `oar.db` stops the start too (a database renamed to
|
|
396
|
+
`oar.db` later would take its pages in, losing the rows written since):
|
|
397
|
+
move it into a folder of your own.
|
|
398
|
+
- **Migrations and `npm run deepseek:import` run by hand never create an
|
|
399
|
+
empty database.** Without a path they take the relay's database in
|
|
400
|
+
`server/data` (`oar.db`, else `copilot.db`); a path given must exist.
|
|
401
|
+
- **`oar uninstall --purge` says it deletes the database backups** with the
|
|
402
|
+
state root.
|
|
403
|
+
- **The DeepSeek card's "Tokens by model" is now "Priced tokens by model"**:
|
|
404
|
+
the cache hits, misses and output its spend estimate prices, beside the
|
|
405
|
+
Tokens sections every tab gets.
|
|
406
|
+
- **The Claude API card's "Tokens by model" is replaced** by the Tokens
|
|
407
|
+
sections every tab gets, which show cache writes apart instead of inside
|
|
408
|
+
"other input". Its spend by model and the cap stay.
|
|
409
|
+
- **Cache writes are no longer lost** for the Claude family, Copilot, Cursor
|
|
410
|
+
and Grok: the token booking keeps them apart. The older bookings (the Claude
|
|
411
|
+
API spend, the DeepSeek estimate) still count them as misses, unchanged.
|
|
412
|
+
- **The Claude API spend no longer counts a turn twice** when Claude Code
|
|
413
|
+
reported one of its results without per-model usage: the next result's
|
|
414
|
+
growth used to include that turn again.
|
|
415
|
+
- **The systemd service restarts the relay with new code, waits for running
|
|
416
|
+
turns, and keeps session workers.** A relay under the systemd user service
|
|
417
|
+
hands a restart (**🌄 Restart web relay**, the **Update** button,
|
|
418
|
+
`oar update`, `oar restart`) to systemd once its turns are done, which
|
|
419
|
+
starts a fresh supervisor and runtime from the code on disk; before, only
|
|
420
|
+
the runtime was relaunched and the supervisor kept its old code. `oar stop`
|
|
421
|
+
and `oar restart` under the service now go through the relay too and wait
|
|
422
|
+
for running turns (the unit stays enabled after a stop); `--now` goes
|
|
423
|
+
through `systemctl --user` at once, as both did before, and so does a relay
|
|
424
|
+
that does not answer. Without the service, `--now` asks the relay not to
|
|
425
|
+
wait, as **Restart now** does. The relay starts its session workers in
|
|
426
|
+
scopes of their own (`systemd-run --user --scope`), so a stop or restart of
|
|
427
|
+
the service no longer ends them. Detached helpers the relay runs for a
|
|
428
|
+
while (a provider CLI being installed, a provider login) still end with a
|
|
429
|
+
full restart of the service. A stop that comes in while the restart is
|
|
430
|
+
handed to systemd wins: no restart follows. On a host with cgroup v1 next to
|
|
431
|
+
an unused v2 hierarchy (a bare `0::/` line) the relay still finds its unit.
|
|
432
|
+
- **The unit restarts a failing relay with a backoff and never gives up.**
|
|
433
|
+
The relay's supervisor relaunches the first two crashes itself; then
|
|
434
|
+
systemd starts it again after 2 seconds, with longer waits up to 5 minutes
|
|
435
|
+
(systemd before 254: every 10 seconds). The unit has `KillMode=mixed`, so
|
|
436
|
+
no runtime of a dead supervisor is left holding the port.
|
|
437
|
+
- **`oar service` works in a git checkout on Linux.**
|
|
438
|
+
`node bin/oar.js service install` writes a unit that runs
|
|
439
|
+
`node server/server.js` in the checkout, on its own config, data and logs;
|
|
440
|
+
`node bin/oar.js start|stop|restart|status` go through it. A hand-made unit
|
|
441
|
+
counts as the checkout's service only when its `ExecStart` runs
|
|
442
|
+
`<checkout>/server/server.js`.
|
|
443
|
+
- **Hand-made units are left as they are.** A unit OAR did not write for this
|
|
444
|
+
install (edited or written by hand, or another install's, such as the
|
|
445
|
+
global install beside a checkout) is no longer written over by
|
|
446
|
+
`oar service install` or `oar setup --start`, or deleted by
|
|
447
|
+
`oar service remove`: they name it and change nothing. `--replace` replaces
|
|
448
|
+
or removes it, keeping a copy as `oar.service.bak`. `oar service status`
|
|
449
|
+
says whose the unit is, lists the drop-ins in `oar.service.d/` (your own
|
|
450
|
+
settings, never touched), and says when lingering is off.
|
|
451
|
+
|
|
452
|
+
### Fixed
|
|
453
|
+
|
|
454
|
+
- **Archiving or deleting a chat whose session worker is gone no longer
|
|
455
|
+
fails with "Couldn't stop this conversation's session".** The relay still
|
|
456
|
+
tried to stop the worker it last knew of; on Windows that stop could fail
|
|
457
|
+
on its own and answered 409 `worker-stop-failed` until a later try. A
|
|
458
|
+
worker whose process has ended counts as stopped. When a stop does fail,
|
|
459
|
+
the relay log says why.
|
|
460
|
+
- **An open fold in the Inbox no longer closes by itself.** Every change of
|
|
461
|
+
a question and the minute's check of paired relays drew the Inbox anew,
|
|
462
|
+
with every **Details** and plan fold closed. An open fold now stays open
|
|
463
|
+
until you close it or its item leaves the list; the same for a question's
|
|
464
|
+
**Details** on its card in the session.
|
|
465
|
+
|
|
466
|
+
### Upgrade notes
|
|
467
|
+
|
|
468
|
+
- **Under the systemd service, run `oar service install` after updating**
|
|
469
|
+
(`node bin/oar.js service install` in a checkout) to get the new unit. A
|
|
470
|
+
hand-made unit is reported and kept; `oar service install --replace` puts
|
|
471
|
+
OAR's in its place, keeping a copy as `oar.service.bak` (drop-ins in
|
|
472
|
+
`oar.service.d/` stay and keep applying).
|
|
473
|
+
- **A checkout's hand-made unit that only runs in the checkout is not its
|
|
474
|
+
service.** A unit with `WorkingDirectory=<checkout>` whose `ExecStart` does
|
|
475
|
+
not name `<checkout>/server/server.js` (`npm start`, a relative
|
|
476
|
+
`server/server.js`) is kept as hand-made: `node bin/oar.js start` and
|
|
477
|
+
`restart` refuse, and `status` says it is not for this checkout. Run
|
|
478
|
+
`node bin/oar.js service install --replace` to put OAR's unit in its place,
|
|
479
|
+
keeping a copy as `oar.service.bak`.
|
|
480
|
+
- **The supervisor of 0.9.19 keeps running until the service restarts once
|
|
481
|
+
by systemd:** at an idle moment, run `oar restart --now`. That restart ends
|
|
482
|
+
running turns once; after it, restarts wait for them.
|
|
483
|
+
- **Session workers started before that restart may stop at it, once.** They
|
|
484
|
+
were started inside the service; those started afterwards run in scopes of
|
|
485
|
+
their own.
|
|
486
|
+
- **Nothing to do for the new database name.** A relay that ran on
|
|
487
|
+
`copilot.db` goes on running on it. To give it the new name, by hand and
|
|
488
|
+
with the relay stopped: take a backup (`oar backup --reason pre-rename`),
|
|
489
|
+
stop the relay fully (`oar stop`, or the systemd service or autostart, so
|
|
490
|
+
nothing starts it again), check that no program holds the files (Linux:
|
|
491
|
+
`fuser copilot.db*`; Windows: close database browsers and Explorer previews),
|
|
492
|
+
make sure no `oar.db-wal` or `oar.db-shm` is there already (move one that
|
|
493
|
+
is into a folder of your own), rename `copilot.db`, and `copilot.db-wal`
|
|
494
|
+
and `copilot.db-shm` where they are there, to `oar.db`, `oar.db-wal`,
|
|
495
|
+
`oar.db-shm`, and start again.
|
|
496
|
+
`oar doctor` then shows `oar.db`. Scripts of your own that open
|
|
497
|
+
`copilot.db` by path need the new name first.
|
|
498
|
+
- **Going back to 0.9.19 on `copilot.db` needs no step.** Migrations 0035 and
|
|
499
|
+
0036 only add the column `conversations.plan_only`, the tables
|
|
500
|
+
`turn_token_usage` and `token_usage_days` and an index; 0.9.19 opens the
|
|
501
|
+
database as it is and ignores them, so a plan-only lock does not hold on
|
|
502
|
+
0.9.19, and the tokens kept so far are there again at the next 0.9.20
|
|
503
|
+
start. Turns run on 0.9.19 book no tokens.
|
|
504
|
+
- **Going back to a version before 0.9.20 on `oar.db`** (a new 0.9.20 install,
|
|
505
|
+
or one renamed by hand): stop the relay fully, make sure no
|
|
506
|
+
`copilot.db-wal` or `copilot.db-shm` is there already (move one that is
|
|
507
|
+
into a folder of your own), rename `oar.db`, `oar.db-wal` and `oar.db-shm`
|
|
508
|
+
(those there) to `copilot.db`, `copilot.db-wal`, `copilot.db-shm`, then
|
|
509
|
+
start the earlier version. A relay still on
|
|
510
|
+
`copilot.db` needs no step. Skipped, the earlier version starts on a new,
|
|
511
|
+
empty `copilot.db`; back on 0.9.20 that one is set aside if nothing was done
|
|
512
|
+
on it, and the start stops with both names if something was.
|
|
513
|
+
- **The first 0.9.20 start backs up the database.** After `oar update` or a
|
|
514
|
+
`git pull` its name carries the running version and says
|
|
515
|
+
`pre-update-from-unknown`, such as
|
|
516
|
+
`oar-0.9.20-20261103-091500-pre-update-from-unknown.db`, though it holds the
|
|
517
|
+
data of the version before: earlier versions did not record which version
|
|
518
|
+
ran on the data. After an in-app update it carries the version before. The
|
|
519
|
+
time in a backup's name is UTC. It needs
|
|
520
|
+
free space for one more copy of the database; `oar doctor` shows the
|
|
521
|
+
result. Backups made by hand before (`copilot-pre-*`, `main-*`,
|
|
522
|
+
`oar-pre-*`) stay where they are and are never counted or deleted.
|
|
523
|
+
- **After going back to an earlier version and up again, run `oar backup`
|
|
524
|
+
before the upgrade:** versions before 0.9.20 do not record that they ran,
|
|
525
|
+
so the second upgrade sees no version change and takes no copy.
|
|
526
|
+
- **To restore a backup:** stop the relay, move the live database files
|
|
527
|
+
(`oar.db*` or `copilot.db*`) into a folder of your own, copy the backup into
|
|
528
|
+
the data folder (it is one file, no `-wal` or `-shm`), and start the version
|
|
529
|
+
the data is of. Name it as that version runs on it: `copilot.db` for a
|
|
530
|
+
version before 0.9.20, else the name the relay ran on. A
|
|
531
|
+
`pre-update-from-unknown` backup holds the data of the version before the
|
|
532
|
+
one in its name. Open a backup only read-only
|
|
533
|
+
(`sqlite3 'file:<backup>?immutable=1'`).
|
|
534
|
+
|
|
6
535
|
## [0.9.19] — 2026-10-27
|
|
7
536
|
|
|
8
537
|
Highlights: a relay that runs out of memory is restarted instead of staying
|