@oar-sh/oar 0.9.19 → 0.9.20
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 +317 -0
- package/README.md +10 -10
- package/bin/oar.js +391 -97
- 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 +27 -0
- package/server/claude-worker/claude-remote-relay-tool.mjs +1 -0
- package/server/claude-worker/claude-session-process.mjs +3 -0
- 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 +57 -4
- package/server/cursor-worker/cursor-turn-runner.mjs +42 -6
- package/server/db-schema.mjs +18 -0
- package/server/deepseek-worker/deepseek-claude-backend.mjs +14 -0
- package/server/grok-worker/grok-turn-runner.mjs +44 -6
- 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-view.mjs +87 -3
- package/server/public/app/api-client.js +2 -1
- package/server/public/app/bootstrap.js +55 -2
- package/server/public/app/composer-control-state.mjs +12 -2
- package/server/public/app/context-usage-view.mjs +16 -0
- package/server/public/app/conversation-view.js +17 -2
- package/server/public/app/inbox-ui.js +32 -0
- package/server/public/app/inbox-view.mjs +141 -5
- package/server/public/app/plan-board-shared.mjs +49 -0
- package/server/public/app/plan-usage-view.mjs +1 -1
- 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-view.mjs +4 -0
- package/server/public/app/socket-handlers.js +10 -2
- package/server/public/index.html +14 -0
- 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 +4 -3
- package/server/repositories/message-repository.mjs +31 -1
- package/server/repositories/remote-relay-repository.mjs +10 -0
- package/server/repositories/session-repository.mjs +6 -1
- package/server/routes/messages-routes.mjs +87 -0
- package/server/routes/relay-ask-routes.mjs +138 -8
- package/server/routes/relay-board-routes.mjs +55 -40
- package/server/routes/sessions-routes.mjs +103 -7
- package/server/server-runtime.mjs +132 -11
- package/server/services/about-service.mjs +59 -0
- package/server/services/cache-rebuild-service.mjs +7 -0
- 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 +40 -5
- package/server/services/restart-resume-service.mjs +5 -2
- package/server/services/session-worker-launch-service.mjs +12 -3
- package/server/services/session-worker-stop-service.mjs +27 -6
- 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-scope.mjs +77 -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
|
@@ -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,323 @@
|
|
|
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.20] — 2026-10-29
|
|
7
|
+
|
|
8
|
+
Highlights: every plan card gets an "Approve plan" button that approves the
|
|
9
|
+
plan without starting the implementation and keeps the session in plan mode,
|
|
10
|
+
and a session can be locked in plan mode (`planOnly`) so agents cannot take
|
|
11
|
+
it out; plan cards are in the Inbox. Check Usage shows the tokens of every
|
|
12
|
+
turn for every provider: input, output, cache writes and cache reads, for
|
|
13
|
+
today, this month and all time, and by model. The relay backs up its database
|
|
14
|
+
before a new version's migrations and keeps the last two copies; `oar backup`
|
|
15
|
+
takes one by hand. A new database is `oar.db`; an existing install keeps
|
|
16
|
+
running on `copilot.db`, nothing is renamed. Under the systemd service a
|
|
17
|
+
restart hands the relay to systemd with its new code, waits for running turns
|
|
18
|
+
and keeps session workers, and `oar service` works in a git checkout. A paired
|
|
19
|
+
relay's approval cards in the Inbox say what they are about, a card a turn
|
|
20
|
+
there waits on pushes once to your devices, and the badge is current within
|
|
21
|
+
about 20 s. An open fold in the Inbox stays open.
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **"Approve plan" on every plan card.** It is the first button of every
|
|
26
|
+
provider's card (the Copilot CLI's own list gets it too), also on cards that
|
|
27
|
+
were waiting when the relay was updated. It marks the plan approved and
|
|
28
|
+
queues exactly "Plan approved by the user. Do not implement. Finish only the
|
|
29
|
+
plan-only steps you were given, then stop." That one turn runs in agent mode
|
|
30
|
+
(plan mode blocks writes, and the plan doc may need a commit); the session
|
|
31
|
+
stays in plan mode, so the next message plans again. Pressed while the plan
|
|
32
|
+
turn still runs, the follow-up waits for it and runs as its own turn.
|
|
33
|
+
- **Plan-only sessions (`planOnly`).** An optional lock, set only when asked:
|
|
34
|
+
relay_tool `create_session` with `planOnly: true` (no mode named: it starts
|
|
35
|
+
in plan; another mode is refused), `update_session` with `planOnly: true`
|
|
36
|
+
(with `mode: "plan"` unless the session is in plan already), or the owner's
|
|
37
|
+
switch "Plan only: agents can't take this session out of plan mode" in the
|
|
38
|
+
context modal, for every session. Plan mode alone never sets it.
|
|
39
|
+
- An agent's `send` or `update_session` that names another mode gets HTTP
|
|
40
|
+
409, code `plan_only`: "This session is plan-only; agents can't change its
|
|
41
|
+
mode. Clear planOnly first, or ask the user." Nothing is queued or changed
|
|
42
|
+
(an update_session dry run refuses before any Allow card). A send without
|
|
43
|
+
a mode runs in the session's saved mode. An agent's mode change through the preferences route
|
|
44
|
+
is refused (409), and so is any planOnly change there (403).
|
|
45
|
+
- `update_session` with `planOnly: false` counts only in the same call that
|
|
46
|
+
names a mode other than plan (`{planOnly: false, mode: "agent"}`). Alone,
|
|
47
|
+
or with `mode: "plan"`, it is ignored, and on a plan-only session the answer says
|
|
48
|
+
"planOnly unchanged: clear it in the same call as the new mode", so a client that
|
|
49
|
+
sends every property as false cannot drop the lock. The owner's switch
|
|
50
|
+
clears it any time.
|
|
51
|
+
- The owner still changes the mode freely; the mode select shows "Plan 🔒".
|
|
52
|
+
On such a session the card recommends "Approve plan" and asks "This
|
|
53
|
+
session is plan-only. <label> leaves plan mode. Go on?" before "Implement
|
|
54
|
+
in autopilot", "Implement with autopilot fleet" and "Stop here and prompt
|
|
55
|
+
myself".
|
|
56
|
+
- list_sessions and read_session show `planOnly: true`. A compaction keeps
|
|
57
|
+
the lock; a turn cut by a usage limit or a restart goes on in plan, except
|
|
58
|
+
the "Approve plan" turn itself. A plan-only conversation's own agent counts
|
|
59
|
+
as a plan-mode caller during that turn too.
|
|
60
|
+
- Migration 0035 adds `conversations.plan_only`.
|
|
61
|
+
- A relay on 0.9.19 still sends its caller's mode with every `send`: a send
|
|
62
|
+
from a non-plan 0.9.19 caller to a plan-only session is refused. A
|
|
63
|
+
`create_session` with planOnly to a 0.9.19 relay creates the session
|
|
64
|
+
without the lock and says so (`planOnlyWarning`).
|
|
65
|
+
- **Relay event `board_settled`.** A session's subscribers hear when one of
|
|
66
|
+
its plan cards is settled, "Stop here" included, with `boardId`,
|
|
67
|
+
`boardType`, `action`, `actionLabel`, `title` and `queuedMessageId`. The
|
|
68
|
+
line reads:
|
|
69
|
+
|
|
70
|
+
```
|
|
71
|
+
[relay event] session "<title>" (<id>): board_settled "<card title>" → "<label>" (action <action>, board_id <board id>, queued message <message id>)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
For "Stop here" it ends `(action exit_only, board_id <board id>, nothing
|
|
75
|
+
queued)`. It is one of the default event types, also for subscriptions
|
|
76
|
+
stored with the 0.9.19 default; `match` applies to its action, label and
|
|
77
|
+
card title.
|
|
78
|
+
- **Plan cards in the Inbox.** For each session that is neither deleted nor
|
|
79
|
+
archived, its newest pending plan card, with the plan folded and the card's
|
|
80
|
+
own buttons (and the plan-only question); plan cards settled in the last
|
|
81
|
+
day show what was chosen. They count in the badge. A paired relay's Inbox
|
|
82
|
+
shows their title only, with "Open there".
|
|
83
|
+
- **Other relays' approval cards are easier to notice.** A paired relay's
|
|
84
|
+
approval card in the Inbox says what it is about: "New session · the
|
|
85
|
+
session waits", then "relay B · New session requested by 'X': title,
|
|
86
|
+
model" (or loop guard, write, session settings, watch, start sessions),
|
|
87
|
+
still with "Open there" only. The agent's prompt, answer or question never
|
|
88
|
+
leaves its relay. Every relay now checks its paired relays about every
|
|
89
|
+
20 s, even with no browser open, so the badge is current within about
|
|
90
|
+
20 s. A card a turn there waits on (an approval, or a question the turn is
|
|
91
|
+
blocked on) pushes once in total: its own relay pushes as before, and when
|
|
92
|
+
that reached none of your devices, the first paired relay with push on
|
|
93
|
+
that checks it pushes instead (`Approval waiting on relay "B"`), with no
|
|
94
|
+
answer buttons; the tap opens the session there. It follows the device's
|
|
95
|
+
"questions" toggle and notification settings: with previews off, an
|
|
96
|
+
approval's body carries no summary, only its kind ("New session · the
|
|
97
|
+
session waits"); the title names the session only with the device's
|
|
98
|
+
include-title setting on.
|
|
99
|
+
- `GET /api/relay-asks/remote/inbox`: approval items gain `variant` and
|
|
100
|
+
`summary`; with `?push=1` (the caller can push), pending approval and
|
|
101
|
+
ask_user items gain `notice` (`here|wait|you|other`), and `you` gains
|
|
102
|
+
`claimed` (`now|before`). A 0.9.19 relay ignores both and never pushes
|
|
103
|
+
another relay's card. New socket event `inbox_remote_changed`. A push's
|
|
104
|
+
data may carry `url`. Claims are in memory; no migration.
|
|
105
|
+
- **The relay backs up its database before a new version's migrations.**
|
|
106
|
+
At the first start of each new version (in a git checkout: of each new
|
|
107
|
+
commit) it copies the database into `backups/` in the state root
|
|
108
|
+
(`~/.oar/backups`, `%APPDATA%\oar\backups`; `server/backups/` in a
|
|
109
|
+
checkout) before the migrations run, as one checked file named after the
|
|
110
|
+
version that ran on the data, such as
|
|
111
|
+
`oar-0.9.19-20261103-091500-pre-update.db`. It keeps the last two
|
|
112
|
+
(`dbBackups.keep` in `config.json`; `dbBackups.enabled: false` turns it off,
|
|
113
|
+
`dbBackups.dir` moves the folder) and deletes only files it wrote itself:
|
|
114
|
+
hand-made backups, `MIGRATION.md` and the `backups/<time>/` folders of
|
|
115
|
+
earlier moves stay as they are, and `oar doctor` lists them as not rotated.
|
|
116
|
+
When the disk would keep less than max(1 GiB, 5 %) free, or the folder holds
|
|
117
|
+
a `DO_NOT_TOUCH` file, the copy is skipped with a warning in the log, in
|
|
118
|
+
About, in `oar doctor` and in the in-app update's log, and the start goes on.
|
|
119
|
+
Plain restarts take no copy. While it runs, `oar start` says the relay is
|
|
120
|
+
backing up its database. A start while `oar backup` writes a copy waits
|
|
121
|
+
for it (up to 15 minutes; `oar start` says so and leaves it running), then
|
|
122
|
+
makes its own in any case.
|
|
123
|
+
- **`oar backup [--reason <text>]`.** Copies the database while the relay
|
|
124
|
+
runs, into the same folder with the same name and rotation
|
|
125
|
+
(`oar-<version>-<time>-manual.db`, or the reason given).
|
|
126
|
+
- **About and `oar doctor` show the database file** by its full path with its
|
|
127
|
+
size, and the backups: the last one, how many are kept, and the folder.
|
|
128
|
+
|
|
129
|
+
- **Tokens per turn for every provider, in Check Usage.** Every tab gets a
|
|
130
|
+
**Tokens** section (today, this month, all time) and **Tokens by model,
|
|
131
|
+
this month**, with fresh input, output, cache writes and cache reads apart
|
|
132
|
+
and the share of input the cache served. A figure a provider does not report
|
|
133
|
+
reads "—" with the reason in the section's note; 0 means reported zero.
|
|
134
|
+
Copilot turns on your own OpenAI key show as their own group, "Tokens — own
|
|
135
|
+
OpenAI key". **About OAR** shows each provider's tokens of all time,
|
|
136
|
+
Claude, Claude API and DeepSeek included. Each turn's tokens are kept per
|
|
137
|
+
run and model with the chat and go when it is deleted; the daily sums stay.
|
|
138
|
+
The Claude API and DeepSeek days from before the upgrade are copied in once,
|
|
139
|
+
with cache writes inside input. Both sections open expanded. Two model ids
|
|
140
|
+
with one name (a dated and an undated Claude id) are one row, their tokens
|
|
141
|
+
added up; pointing at the row shows the ids.
|
|
142
|
+
- Detail rows in `GET /api/usage` may carry a `title`: on a merged Tokens
|
|
143
|
+
by model row, `Model ids: <id>, <id>`. Those rows are sorted by name.
|
|
144
|
+
- New worker route `POST /api/turn-token-usage` and tables `turn_token_usage`
|
|
145
|
+
and `token_usage_days` (migration 0036). Every worker posts after each
|
|
146
|
+
turn: Claude, Claude API, DeepSeek, Claude cloud, Copilot (plan and own
|
|
147
|
+
key), Cursor and Grok.
|
|
148
|
+
- `OAR_DEBUG_TOKEN_USAGE=1` makes the Copilot worker log each model call's
|
|
149
|
+
raw token counts (no ids).
|
|
150
|
+
|
|
151
|
+
### Changed
|
|
152
|
+
|
|
153
|
+
- **No plan summary in a card's follow-up.** "Implement in autopilot",
|
|
154
|
+
"Implement with autopilot fleet" and "Stop here and prompt myself" queue
|
|
155
|
+
only their line; the plan is no longer repeated after it.
|
|
156
|
+
- **relay_tool `send` without `mode` leaves the target's mode alone.** It
|
|
157
|
+
used to run the target's turn in the calling session's mode (a plan-mode
|
|
158
|
+
agent put the sessions it prompted into plan mode). Name `mode` when a turn
|
|
159
|
+
should run in a given one. `create_session` without a mode still takes the
|
|
160
|
+
calling session's.
|
|
161
|
+
- **Switching between plan mode and a writing mode during a running turn
|
|
162
|
+
queues the message instead of steering it in.** A steer switched the running
|
|
163
|
+
turn to the message's mode, so a plan turn could gain write access halfway
|
|
164
|
+
through, or a writing turn lose it. Such a message now waits and runs as its
|
|
165
|
+
own turn once the current one ends; the composer reads "Queue" for it.
|
|
166
|
+
Messages in a mode on the same side (agent, ask and autopilot with each
|
|
167
|
+
other, or plan with plan) steer in as before.
|
|
168
|
+
- **A new database is `oar.db`.** The relay's database file gets the
|
|
169
|
+
product's name: a database created from nothing is `oar.db` (with
|
|
170
|
+
`oar.db-wal` and `oar.db-shm`). An existing install keeps running on
|
|
171
|
+
`copilot.db`; nothing is renamed, and `oar doctor` and About show which file
|
|
172
|
+
a relay runs on. A database brought over from elsewhere (an earlier
|
|
173
|
+
install's package, `oar setup --migrate-from`) keeps its own name.
|
|
174
|
+
- **`oar.db` and `copilot.db` in one data folder.** When
|
|
175
|
+
`oar.db` and `copilot.db` are both there, the start reads both. A
|
|
176
|
+
`copilot.db` that is not newer, is empty, or holds only what a start of an
|
|
177
|
+
earlier version writes (no conversation, message or other work; a few
|
|
178
|
+
start-up settings) is set aside as
|
|
179
|
+
`copilot.db.older-<time>`, never deleted, and the relay runs on `oar.db`.
|
|
180
|
+
A `copilot.db` with newer work, or each newer somewhere, stops the start
|
|
181
|
+
(exit code 78) with both files named and nothing changed: move the one not
|
|
182
|
+
to go on with into a folder of your own, then start again. An `oar.db-wal`
|
|
183
|
+
or `oar.db-shm` without `oar.db` stops the start too (a database renamed to
|
|
184
|
+
`oar.db` later would take its pages in, losing the rows written since):
|
|
185
|
+
move it into a folder of your own.
|
|
186
|
+
- **Migrations and `npm run deepseek:import` run by hand never create an
|
|
187
|
+
empty database.** Without a path they take the relay's database in
|
|
188
|
+
`server/data` (`oar.db`, else `copilot.db`); a path given must exist.
|
|
189
|
+
- **`oar uninstall --purge` says it deletes the database backups** with the
|
|
190
|
+
state root.
|
|
191
|
+
- **The DeepSeek card's "Tokens by model" is now "Priced tokens by model"**:
|
|
192
|
+
the cache hits, misses and output its spend estimate prices, beside the
|
|
193
|
+
Tokens sections every tab gets.
|
|
194
|
+
- **The Claude API card's "Tokens by model" is replaced** by the Tokens
|
|
195
|
+
sections every tab gets, which show cache writes apart instead of inside
|
|
196
|
+
"other input". Its spend by model and the cap stay.
|
|
197
|
+
- **Cache writes are no longer lost** for the Claude family, Copilot, Cursor
|
|
198
|
+
and Grok: the token booking keeps them apart. The older bookings (the Claude
|
|
199
|
+
API spend, the DeepSeek estimate) still count them as misses, unchanged.
|
|
200
|
+
- **The Claude API spend no longer counts a turn twice** when Claude Code
|
|
201
|
+
reported one of its results without per-model usage: the next result's
|
|
202
|
+
growth used to include that turn again.
|
|
203
|
+
- **The systemd service restarts the relay with new code, waits for running
|
|
204
|
+
turns, and keeps session workers.** A relay under the systemd user service
|
|
205
|
+
hands a restart (**🌄 Restart web relay**, the **Update** button,
|
|
206
|
+
`oar update`, `oar restart`) to systemd once its turns are done, which
|
|
207
|
+
starts a fresh supervisor and runtime from the code on disk; before, only
|
|
208
|
+
the runtime was relaunched and the supervisor kept its old code. `oar stop`
|
|
209
|
+
and `oar restart` under the service now go through the relay too and wait
|
|
210
|
+
for running turns (the unit stays enabled after a stop); `--now` goes
|
|
211
|
+
through `systemctl --user` at once, as both did before, and so does a relay
|
|
212
|
+
that does not answer. Without the service, `--now` asks the relay not to
|
|
213
|
+
wait, as **Restart now** does. The relay starts its session workers in
|
|
214
|
+
scopes of their own (`systemd-run --user --scope`), so a stop or restart of
|
|
215
|
+
the service no longer ends them. Detached helpers the relay runs for a
|
|
216
|
+
while (a provider CLI being installed, a provider login) still end with a
|
|
217
|
+
full restart of the service. A stop that comes in while the restart is
|
|
218
|
+
handed to systemd wins: no restart follows. On a host with cgroup v1 next to
|
|
219
|
+
an unused v2 hierarchy (a bare `0::/` line) the relay still finds its unit.
|
|
220
|
+
- **The unit restarts a failing relay with a backoff and never gives up.**
|
|
221
|
+
The relay's supervisor relaunches the first two crashes itself; then
|
|
222
|
+
systemd starts it again after 2 seconds, with longer waits up to 5 minutes
|
|
223
|
+
(systemd before 254: every 10 seconds). The unit has `KillMode=mixed`, so
|
|
224
|
+
no runtime of a dead supervisor is left holding the port.
|
|
225
|
+
- **`oar service` works in a git checkout on Linux.**
|
|
226
|
+
`node bin/oar.js service install` writes a unit that runs
|
|
227
|
+
`node server/server.js` in the checkout, on its own config, data and logs;
|
|
228
|
+
`node bin/oar.js start|stop|restart|status` go through it. A hand-made unit
|
|
229
|
+
counts as the checkout's service only when its `ExecStart` runs
|
|
230
|
+
`<checkout>/server/server.js`.
|
|
231
|
+
- **Hand-made units are left as they are.** A unit OAR did not write for this
|
|
232
|
+
install (edited or written by hand, or another install's, such as the
|
|
233
|
+
global install beside a checkout) is no longer written over by
|
|
234
|
+
`oar service install` or `oar setup --start`, or deleted by
|
|
235
|
+
`oar service remove`: they name it and change nothing. `--replace` replaces
|
|
236
|
+
or removes it, keeping a copy as `oar.service.bak`. `oar service status`
|
|
237
|
+
says whose the unit is, lists the drop-ins in `oar.service.d/` (your own
|
|
238
|
+
settings, never touched), and says when lingering is off.
|
|
239
|
+
|
|
240
|
+
### Fixed
|
|
241
|
+
|
|
242
|
+
- **Archiving or deleting a chat whose session worker is gone no longer
|
|
243
|
+
fails with "Couldn't stop this conversation's session".** The relay still
|
|
244
|
+
tried to stop the worker it last knew of; on Windows that stop could fail
|
|
245
|
+
on its own and answered 409 `worker-stop-failed` until a later try. A
|
|
246
|
+
worker whose process has ended counts as stopped. When a stop does fail,
|
|
247
|
+
the relay log says why.
|
|
248
|
+
- **An open fold in the Inbox no longer closes by itself.** Every change of
|
|
249
|
+
a question and the minute's check of paired relays drew the Inbox anew,
|
|
250
|
+
with every **Details** and plan fold closed. An open fold now stays open
|
|
251
|
+
until you close it or its item leaves the list; the same for a question's
|
|
252
|
+
**Details** on its card in the session.
|
|
253
|
+
|
|
254
|
+
### Upgrade notes
|
|
255
|
+
|
|
256
|
+
- **Under the systemd service, run `oar service install` after updating**
|
|
257
|
+
(`node bin/oar.js service install` in a checkout) to get the new unit. A
|
|
258
|
+
hand-made unit is reported and kept; `oar service install --replace` puts
|
|
259
|
+
OAR's in its place, keeping a copy as `oar.service.bak` (drop-ins in
|
|
260
|
+
`oar.service.d/` stay and keep applying).
|
|
261
|
+
- **A checkout's hand-made unit that only runs in the checkout is not its
|
|
262
|
+
service.** A unit with `WorkingDirectory=<checkout>` whose `ExecStart` does
|
|
263
|
+
not name `<checkout>/server/server.js` (`npm start`, a relative
|
|
264
|
+
`server/server.js`) is kept as hand-made: `node bin/oar.js start` and
|
|
265
|
+
`restart` refuse, and `status` says it is not for this checkout. Run
|
|
266
|
+
`node bin/oar.js service install --replace` to put OAR's unit in its place,
|
|
267
|
+
keeping a copy as `oar.service.bak`.
|
|
268
|
+
- **The supervisor of 0.9.19 keeps running until the service restarts once
|
|
269
|
+
by systemd:** at an idle moment, run `oar restart --now`. That restart ends
|
|
270
|
+
running turns once; after it, restarts wait for them.
|
|
271
|
+
- **Session workers started before that restart may stop at it, once.** They
|
|
272
|
+
were started inside the service; those started afterwards run in scopes of
|
|
273
|
+
their own.
|
|
274
|
+
- **Nothing to do for the new database name.** A relay that ran on
|
|
275
|
+
`copilot.db` goes on running on it. To give it the new name, by hand and
|
|
276
|
+
with the relay stopped: take a backup (`oar backup --reason pre-rename`),
|
|
277
|
+
stop the relay fully (`oar stop`, or the systemd service or autostart, so
|
|
278
|
+
nothing starts it again), check that no program holds the files (Linux:
|
|
279
|
+
`fuser copilot.db*`; Windows: close database browsers and Explorer previews),
|
|
280
|
+
make sure no `oar.db-wal` or `oar.db-shm` is there already (move one that
|
|
281
|
+
is into a folder of your own), rename `copilot.db`, and `copilot.db-wal`
|
|
282
|
+
and `copilot.db-shm` where they are there, to `oar.db`, `oar.db-wal`,
|
|
283
|
+
`oar.db-shm`, and start again.
|
|
284
|
+
`oar doctor` then shows `oar.db`. Scripts of your own that open
|
|
285
|
+
`copilot.db` by path need the new name first.
|
|
286
|
+
- **Going back to 0.9.19 on `copilot.db` needs no step.** Migrations 0035 and
|
|
287
|
+
0036 only add the column `conversations.plan_only`, the tables
|
|
288
|
+
`turn_token_usage` and `token_usage_days` and an index; 0.9.19 opens the
|
|
289
|
+
database as it is and ignores them, so a plan-only lock does not hold on
|
|
290
|
+
0.9.19, and the tokens kept so far are there again at the next 0.9.20
|
|
291
|
+
start. Turns run on 0.9.19 book no tokens.
|
|
292
|
+
- **Going back to a version before 0.9.20 on `oar.db`** (a new 0.9.20 install,
|
|
293
|
+
or one renamed by hand): stop the relay fully, make sure no
|
|
294
|
+
`copilot.db-wal` or `copilot.db-shm` is there already (move one that is
|
|
295
|
+
into a folder of your own), rename `oar.db`, `oar.db-wal` and `oar.db-shm`
|
|
296
|
+
(those there) to `copilot.db`, `copilot.db-wal`, `copilot.db-shm`, then
|
|
297
|
+
start the earlier version. A relay still on
|
|
298
|
+
`copilot.db` needs no step. Skipped, the earlier version starts on a new,
|
|
299
|
+
empty `copilot.db`; back on 0.9.20 that one is set aside if nothing was done
|
|
300
|
+
on it, and the start stops with both names if something was.
|
|
301
|
+
- **The first 0.9.20 start backs up the database.** After `oar update` or a
|
|
302
|
+
`git pull` its name carries the running version and says
|
|
303
|
+
`pre-update-from-unknown`, such as
|
|
304
|
+
`oar-0.9.20-20261103-091500-pre-update-from-unknown.db`, though it holds the
|
|
305
|
+
data of the version before: earlier versions did not record which version
|
|
306
|
+
ran on the data. After an in-app update it carries the version before. The
|
|
307
|
+
time in a backup's name is UTC. It needs
|
|
308
|
+
free space for one more copy of the database; `oar doctor` shows the
|
|
309
|
+
result. Backups made by hand before (`copilot-pre-*`, `main-*`,
|
|
310
|
+
`oar-pre-*`) stay where they are and are never counted or deleted.
|
|
311
|
+
- **After going back to an earlier version and up again, run `oar backup`
|
|
312
|
+
before the upgrade:** versions before 0.9.20 do not record that they ran,
|
|
313
|
+
so the second upgrade sees no version change and takes no copy.
|
|
314
|
+
- **To restore a backup:** stop the relay, move the live database files
|
|
315
|
+
(`oar.db*` or `copilot.db*`) into a folder of your own, copy the backup into
|
|
316
|
+
the data folder (it is one file, no `-wal` or `-shm`), and start the version
|
|
317
|
+
the data is of. Name it as that version runs on it: `copilot.db` for a
|
|
318
|
+
version before 0.9.20, else the name the relay ran on. A
|
|
319
|
+
`pre-update-from-unknown` backup holds the data of the version before the
|
|
320
|
+
one in its name. Open a backup only read-only
|
|
321
|
+
(`sqlite3 'file:<backup>?immutable=1'`).
|
|
322
|
+
|
|
6
323
|
## [0.9.19] — 2026-10-27
|
|
7
324
|
|
|
8
325
|
Highlights: a relay that runs out of memory is restarted instead of staying
|
package/README.md
CHANGED
|
@@ -45,29 +45,29 @@ flowchart LR
|
|
|
45
45
|
<table>
|
|
46
46
|
<tr>
|
|
47
47
|
<td width="42%" rowspan="4" valign="top">
|
|
48
|
-
<a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.
|
|
48
|
+
<a href="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-mobile.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-mobile.png?v=0.9.20" alt="OAR on a phone: a finished agent turn in a conversation, with the composer below"></picture></a>
|
|
49
49
|
<br><sub>On a phone: the same conversations, installable as an app.</sub>
|
|
50
50
|
</td>
|
|
51
51
|
<td width="58%" valign="top">
|
|
52
|
-
<a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.
|
|
52
|
+
<a href="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-agents.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-agents.png?v=0.9.20" alt="Background subagents running in parallel in the task panel, each with live token counts, elapsed time, and its own Stop button"></picture></a>
|
|
53
53
|
<br><sub>Agents working in parallel, each with live token counts and its own Stop.</sub>
|
|
54
54
|
</td>
|
|
55
55
|
</tr>
|
|
56
56
|
<tr>
|
|
57
57
|
<td valign="top">
|
|
58
|
-
<a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.
|
|
58
|
+
<a href="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-question.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-question.png?v=0.9.20" alt="A question card in the middle of a turn: the agent offers several choices and a free-text reply"></picture></a>
|
|
59
59
|
<br><sub>When the agent needs a decision, it asks, and waits for your answer.</sub>
|
|
60
60
|
</td>
|
|
61
61
|
</tr>
|
|
62
62
|
<tr>
|
|
63
63
|
<td valign="top">
|
|
64
|
-
<a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.
|
|
64
|
+
<a href="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pingcf-thoughts.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/pingcf-thoughts.png?v=0.9.20" alt="An expanded Thoughts block showing the agent's reasoning above its reply"></picture></a>
|
|
65
65
|
<br><sub>Read the reasoning, not just the answer.</sub>
|
|
66
66
|
</td>
|
|
67
67
|
</tr>
|
|
68
68
|
<tr>
|
|
69
69
|
<td valign="top">
|
|
70
|
-
<a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.
|
|
70
|
+
<a href="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-watching.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/pagewright-watching.png?v=0.9.20" alt="A session watching two other sessions: a finished reply arrives as a compact relay-event row, the watching 2 sessions list is open with Unsubscribe, and a request to also watch a session on a paired relay waits on an Allow / Deny card"></picture></a>
|
|
71
71
|
<br><sub>Agents that watch agents: woken by events, approving nothing.</sub>
|
|
72
72
|
</td>
|
|
73
73
|
</tr>
|
|
@@ -86,7 +86,7 @@ flowchart LR
|
|
|
86
86
|
|
|
87
87
|
You need Node.js 22.13 or newer (the macOS and Linux installer brings its own when yours is missing or older) and an account with the agent you want to use; see [Prerequisites](docs/install.md#prerequisites). There are also installs [with npm](docs/install.md#npm) and [from a git checkout](docs/install.md#from-a-git-checkout).
|
|
88
88
|
|
|
89
|
-
`oar` alone opens a menu. `oar start`, `stop`, `restart` and `status` control the relay, `oar service install` starts it at boot or sign-in (a systemd user service on Linux, the sign-in autostart on Windows), `oar doctor` shows what it uses, and `oar update` installs a new release (as does **⚙️ Settings → General → Check for updates**). All the details are in [Installing and running OAR](docs/install.md): [the `oar` command](docs/install.md#the-oar-command), [where OAR keeps its files](docs/install.md#where-oar-keeps-its-files), and [updating](docs/install.md#updating).
|
|
89
|
+
`oar` alone opens a menu. `oar start`, `stop`, `restart` and `status` control the relay, `oar service install` starts it at boot or sign-in and keeps it running (a systemd user service on Linux, also for a git checkout; the sign-in autostart on Windows), `oar doctor` shows what it uses, and `oar update` installs a new release (as does **⚙️ Settings → General → Check for updates**). All the details are in [Installing and running OAR](docs/install.md): [the `oar` command](docs/install.md#the-oar-command), [where OAR keeps its files](docs/install.md#where-oar-keeps-its-files), and [updating](docs/install.md#updating).
|
|
90
90
|
|
|
91
91
|
<a id="providers"></a>
|
|
92
92
|
<a id="claude-cloud"></a>
|
|
@@ -118,11 +118,11 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
118
118
|
<table>
|
|
119
119
|
<tr>
|
|
120
120
|
<td width="62%" valign="top">
|
|
121
|
-
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.
|
|
121
|
+
<a href="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/cfmail-steering.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/cfmail-steering.png?v=0.9.20" alt="A running Claude turn with a message steered into it mid-turn, the reply streaming with its tool calls, and the Steer button in the composer"></picture></a>
|
|
122
122
|
<br><sub>Send a message while the agent works, and it steers the running turn.</sub>
|
|
123
123
|
</td>
|
|
124
124
|
<td width="38%" valign="top">
|
|
125
|
-
<a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.
|
|
125
|
+
<a href="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/usage-copilot-compact.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/usage-copilot-compact.png?v=0.9.20" alt="The GitHub Copilot card in Check Usage: premium requests and plan quota with reset dates, and the cost of the last turn"></picture></a>
|
|
126
126
|
<br><sub>What each plan has left, in one place.</sub>
|
|
127
127
|
</td>
|
|
128
128
|
</tr>
|
|
@@ -139,7 +139,7 @@ API keys are entered in **⚙️ Settings** and stay on the relay host. The Clau
|
|
|
139
139
|
- **Share** a conversation as a read-only link, hide single messages and the host's paths from it, and see when it was opened ([Sharing](docs/web-ui.md#sharing)).
|
|
140
140
|
- **On your phone**: install OAR as an app and get push notifications ([Notifications and the app](docs/web-ui.md#notifications-and-the-app)).
|
|
141
141
|
|
|
142
|
-
The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.
|
|
142
|
+
The full tour is in [Using the web UI](docs/web-ui.md); every setting and config key is in [Settings and configuration](docs/configuration.md). What changed in each release is in the [changelog](CHANGELOG.md), with [0.9.20](CHANGELOG.md#0920--2026-10-29) as the latest.
|
|
143
143
|
|
|
144
144
|
## Remote access
|
|
145
145
|
|
|
@@ -190,7 +190,7 @@ flowchart LR
|
|
|
190
190
|
|
|
191
191
|
You stay in charge: per paired relay you decide whether agents may only read, also prompt, or do everything, and in the `ask` and `plan` modes every write asks you first. A subscription only notifies; it approves nothing.
|
|
192
192
|
|
|
193
|
-
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.
|
|
193
|
+
<p><a href="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.20"><picture><source media="(prefers-color-scheme: light)" srcset="https://oar.sh/assets/shots/pagewright-cross-relay.light.png?v=0.9.20"><img src="https://oar.sh/assets/shots/pagewright-cross-relay.png?v=0.9.20" alt="A session watching two sessions on a paired relay: a reply arrives as a relay-event row, and after a short outage the events the other relay kept arrive as one replayed row" width="720"></picture></a></p>
|
|
194
194
|
|
|
195
195
|
How pairing, permissions and limits work is in [Agents working together](docs/agents-together.md).
|
|
196
196
|
|