@awebai/oats 0.41.1 → 0.42.0
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/bin/oats.mjs +3 -19
- package/docs/capabilities.md +73 -2
- package/docs/capability-manifest.schema.json +38 -0
- package/docs/desktop-cli-api.md +89 -6
- package/docs/desktop.md +15 -5
- package/docs/execution-targets.md +202 -45
- package/docs/implementation.md +3 -1
- package/docs/release-notes/v0.42.0.md +398 -0
- package/docs/souls-and-instances.md +315 -2
- package/lib/capability-contract.mjs +56 -0
- package/lib/core.mjs +1214 -264
- package/lib/instance-lifecycle.mjs +12 -1
- package/lib/launch-preference.mjs +9 -0
- package/lib/login-environment.mjs +212 -0
- package/lib/retire-output.mjs +50 -0
- package/lib/tree-copy.mjs +4 -2
- package/package.json +1 -1
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
# OATS 0.42.0
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
|
|
5
|
+
- **A capability can declare the home state it owns:
|
|
6
|
+
`retirement.disposable.home`**
|
|
7
|
+
([#410](https://github.com/awebai/oats/issues/410)). The meaning is:
|
|
8
|
+
provider-owned state that is not the instance's work, left in place until
|
|
9
|
+
the home is removed, and not copied to recovery. The one exception is the
|
|
10
|
+
preservation of a failed spawn in directory mode, which copies the whole
|
|
11
|
+
home, declared entries included
|
|
12
|
+
([#598](https://github.com/awebai/oats/issues/598)). A provider declares the
|
|
13
|
+
top-level entries of the instance home that hold its own state, such as an
|
|
14
|
+
identity directory with a signing key:
|
|
15
|
+
|
|
16
|
+
```json
|
|
17
|
+
"retirement": { "disposable": { "home": [".aw", ".oats-aweb", ".aweb-identity", ".aweb-identity-*"] } }
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Each entry is one hidden top-level name (`.name`) or a prefix
|
|
21
|
+
(`.prefix-*`, every top-level entry whose name starts with the text before
|
|
22
|
+
`*`). Matching entries are left out of the home fingerprint, the home copy
|
|
23
|
+
and its verification, so a change to them alone causes no copy. Exclusion
|
|
24
|
+
means "not copied" and nothing more: the entries stay in the home until the
|
|
25
|
+
home is removed, retire hooks still see them, and an incomplete cleanup
|
|
26
|
+
keeps the home with them. The receipt names what was left out in
|
|
27
|
+
`workRecovery.notCopied` (`[{scope, path, owner}]`: names and owners only),
|
|
28
|
+
and the summary prints `not copied: .aw, .oats-aweb (oats.aweb)`. A
|
|
29
|
+
capability that declares nothing has its home state copied, once. This
|
|
30
|
+
kernel reads the field. See
|
|
31
|
+
[capabilities](../capabilities.md#manifest).
|
|
32
|
+
|
|
33
|
+
## Changed
|
|
34
|
+
|
|
35
|
+
- **Desktop has a theme picker (#602).** The theme button and
|
|
36
|
+
Ctrl+Shift+Space (⇧⌘Space on macOS) open a theme picker in the middle of
|
|
37
|
+
the window: the four themes, the current one marked. Choose one with a
|
|
38
|
+
click, Enter or Space; Escape closes it with nothing changed. The shortcut
|
|
39
|
+
works while a terminal has focus too. The theme button now opens the picker;
|
|
40
|
+
cycling between themes stays in the command palette (**Theme: cycle…**). On
|
|
41
|
+
Omarchy, Super+Ctrl+Shift+Space stays Omarchy's own theme menu (it changes
|
|
42
|
+
the computer's theme, which This computer follows); Ctrl+Shift+Space is its
|
|
43
|
+
in-app counterpart and opens Desktop's picker. A shortcut you had already set
|
|
44
|
+
to the same keys keeps them; the shortcuts editor names the clash.
|
|
45
|
+
- **The Desktop accepts OATS CLIs `>=0.25.8 <0.43.0`**, so it runs against
|
|
46
|
+
this release's kernel; the Desktop 0.41.x refuses a 0.42 CLI.
|
|
47
|
+
- **One retire writes one recovery**
|
|
48
|
+
([#480](https://github.com/awebai/oats/issues/480),
|
|
49
|
+
[#544](https://github.com/awebai/oats/issues/544)). A retire whose hooks
|
|
50
|
+
changed the home or the work wrote a second recovery directory with a full
|
|
51
|
+
copy of both. It now adds to the recovery it already wrote, and copies again
|
|
52
|
+
only the part that changed:
|
|
53
|
+
|
|
54
|
+
```text
|
|
55
|
+
<recovery>/
|
|
56
|
+
recovery.json
|
|
57
|
+
home/ the home before the retire hooks
|
|
58
|
+
repo/ or work/ the work before the retire hooks
|
|
59
|
+
after-hooks/
|
|
60
|
+
home/ the home again, only if a hook changed home bytes
|
|
61
|
+
repo/ or work/ the work again, unless it is proven unchanged
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
The work is copied again unless it is proven unchanged: unchanged means
|
|
65
|
+
that everything a copy of it would carry is equal byte for byte, and
|
|
66
|
+
anything that cannot be compared exactly counts as changed. In worktree
|
|
67
|
+
mode that is what a work copy holds beside the files (status, branch and
|
|
68
|
+
commit; the index's entries, resolve-undo records and permission bits;
|
|
69
|
+
what it copies from the Git directories, with their permission bits: an
|
|
70
|
+
operation in progress, `info/attributes` and the stash's log; tags,
|
|
71
|
+
stash, exclude rules and status settings) and the bytes and permission
|
|
72
|
+
bits of its files. Every part is compared as its bytes, never as
|
|
73
|
+
decoded text, so two states that differ only in bytes that are not valid
|
|
74
|
+
UTF-8 (in a ref name, a path, a file name or the target of a symbolic
|
|
75
|
+
link) are two states. A recovery still cannot hold a file whose name is
|
|
76
|
+
not valid UTF-8: its copy fails and the retire refuses, as before.
|
|
77
|
+
A status with the same rows is not taken as proof, so a retire
|
|
78
|
+
hook that rewrites a file that was already modified has its bytes
|
|
79
|
+
preserved, whether or not it also writes the home
|
|
80
|
+
([#600](https://github.com/awebai/oats/issues/600)). For that proof a
|
|
81
|
+
retire reads the worktree's files once before the hooks, when there is
|
|
82
|
+
something to preserve, and at most once more after them. A tag or a stash
|
|
83
|
+
made in the worktree's repository while the hooks run adds a copy
|
|
84
|
+
attempt. Two things are not compared, and the proof claims no byte
|
|
85
|
+
equality beyond them: the index's derived data (its stat cache and
|
|
86
|
+
extensions), a deliberate semantic exception; and the repository the
|
|
87
|
+
worktree belongs to (its objects, its other branches, the settings a clone
|
|
88
|
+
of it is served under), a custody boundary. It holds because of 0.41's
|
|
89
|
+
retirement ([#640](https://github.com/awebai/oats/issues/640)): the
|
|
90
|
+
repository survives the retire, no retire deletes a branch, and a commit
|
|
91
|
+
of the worktree that no ref reaches is preserved before the worktree is
|
|
92
|
+
removed.
|
|
93
|
+
A worktree that holds a repository (a directory under it with a `.git`
|
|
94
|
+
entry of any kind, a dangling symbolic link included; a repository made
|
|
95
|
+
by `git init` or a clone, a submodule, or a linked worktree of another
|
|
96
|
+
repository) is never proven unchanged: its work is copied again after
|
|
97
|
+
the hooks, whatever they did, and `afterHooks.work` then says that a copy
|
|
98
|
+
was made, not that a hook changed the work. That copy can fail where the
|
|
99
|
+
first did not, and the retire then refuses after the hooks with the home,
|
|
100
|
+
the work and the recovery written before them kept. A worktree with a
|
|
101
|
+
`.git` entry under it that OATS cannot read as a repository (a dangling
|
|
102
|
+
symbolic link, or a `.git` it cannot test) is never home-only, and no
|
|
103
|
+
class is added for it: a change to the home alone can now cause a
|
|
104
|
+
work-copy attempt before the hooks that OATS 0.41 did not make, and any
|
|
105
|
+
failure of that attempt can refuse the retirement, before any retire hook
|
|
106
|
+
runs and with the home and the work kept; a launched instance's session
|
|
107
|
+
has been stopped by then, as the refusal says. Of a nested repository's
|
|
108
|
+
branches other than the one `HEAD` is on, the copy holds the commits and
|
|
109
|
+
not the names: `git fsck --unreachable` in the copy lists them, and a
|
|
110
|
+
commit kept without a name is lost to `git gc` in the copy. Of its stash
|
|
111
|
+
the copy holds the latest entry; of its remote-tracking refs, notes and other
|
|
112
|
+
namespaces only what a branch or a tag reaches; a repository inside it
|
|
113
|
+
comes along as plain files.
|
|
114
|
+
A snapshot that holds the home only also gets the work under
|
|
115
|
+
`after-hooks/` when the hooks did not move it but something beyond the
|
|
116
|
+
home is there to preserve after them. The home's comparison holds every
|
|
117
|
+
entry its copy carries, the kernel's own records included
|
|
118
|
+
(`.oats-events.jsonl`, the `.oats-stop*.json` and `.oats-restart.json`
|
|
119
|
+
receipts, `.oats-agents-md.*.previous`, `.claude/settings.json`, and every
|
|
120
|
+
field of `instance.json`): a hook that writes one of them has the home
|
|
121
|
+
copied again. The home is not copied again because the work is, so when a
|
|
122
|
+
hook moved only the work those records are as of the pre-hook snapshot.
|
|
123
|
+
A home copy is still verified against the digest a spawn baseline uses,
|
|
124
|
+
which passes over those records: the verification does not prove they
|
|
125
|
+
were preserved ([#645](https://github.com/awebai/oats/issues/645)).
|
|
126
|
+
|
|
127
|
+
Each part under `after-hooks/` is whole and verified, verified
|
|
128
|
+
before the home is removed; `after-hooks/home/` exists only when the
|
|
129
|
+
home's own bytes moved. The pre-hook snapshot is never rewritten. An
|
|
130
|
+
instance that had nothing to preserve before the hooks still gets its one
|
|
131
|
+
recovery after them, with no `after-hooks/`. `recovery.json` gains `phase`:
|
|
132
|
+
`"before-hooks"` when the pre-hook snapshot is written, `"complete"` once
|
|
133
|
+
the post-hook check has concluded. A retried retire, after incomplete
|
|
134
|
+
cleanup or after a refused or interrupted attempt, writes its own recovery
|
|
135
|
+
and never touches an earlier one; a directory left at `before-hooks` is a
|
|
136
|
+
complete, verified pre-hook snapshot. The summary prints one "has been
|
|
137
|
+
preserved" block, with what was copied from the home and an `after the
|
|
138
|
+
retire hooks: … copied again under after-hooks/` line when it applies. See
|
|
139
|
+
[souls and instances](../souls-and-instances.md#retire).
|
|
140
|
+
- **A socket, a FIFO or a device file in a worktree refuses the retire.**
|
|
141
|
+
In worktree mode, when a retire has something to preserve before its
|
|
142
|
+
hooks run, it now reads the whole worktree, and an entry that is not a
|
|
143
|
+
file, a directory or a symbolic link cannot be read or copied. Git prints
|
|
144
|
+
no status row for such an entry. Before, a retire refused it only inside
|
|
145
|
+
a directory Git reports as ignored, or when it copied the work; a retire
|
|
146
|
+
that preserved only the home went through. Now it refuses with `E_WORK_INSPECTION_FAILED` before
|
|
147
|
+
any retire hook runs: no recovery was written and nothing was deleted,
|
|
148
|
+
however often it is retried. The message names the entry and says what to
|
|
149
|
+
do: safely stop the process or resource that owns it, or move the entry
|
|
150
|
+
elsewhere, then retry (it may be a live endpoint, so the advice is not to
|
|
151
|
+
delete it).
|
|
152
|
+
`--json` keeps the code; the `message` field is the new sentence, also for
|
|
153
|
+
the cases that were already refused. Those are refused where they were,
|
|
154
|
+
at the first inspection: an entry inside a directory Git reports as
|
|
155
|
+
ignored whole, in the `work/` of a directory instance, or in the home
|
|
156
|
+
itself. That inspection runs before the retire stops the session, so
|
|
157
|
+
there the session is still running, and the message ends after saying
|
|
158
|
+
what to do. Everywhere else in a worktree the refusal comes from the new
|
|
159
|
+
read, after the retire has stopped the session of a launched instance:
|
|
160
|
+
the instance's session is stopped, the instance is not retired, and its
|
|
161
|
+
home is kept; the message says so, and says that this retire stopped no
|
|
162
|
+
session when the instance had none to stop. To continue, deal with the
|
|
163
|
+
entry and run
|
|
164
|
+
`oats retire <instance>` again, or start the session again in the same
|
|
165
|
+
home with `oats session start --home <abs>`. A self-retire (`--self`) is
|
|
166
|
+
refused the same way by its detached completion, which records the
|
|
167
|
+
refusal beside the home; after that only `oats retire <instance>`
|
|
168
|
+
continues, and `oats session start` refuses until it has. Of the refusals
|
|
169
|
+
from the new read, only that of `--self --keep-dir` comes with the session
|
|
170
|
+
still running. When a retire hook leaves such an
|
|
171
|
+
entry behind, the hooks have run: the retire refuses
|
|
172
|
+
(`E_WORK_INSPECTION_FAILED`, or `E_WORK_PRESERVATION_FAILED` when the hook
|
|
173
|
+
also moved the Git state) and the home, the work and the recovery written
|
|
174
|
+
before the hooks are kept.
|
|
175
|
+
- **A file in a worktree that cannot be read refuses a retire that
|
|
176
|
+
preserved only the home.** In worktree mode, when a retire has something
|
|
177
|
+
to preserve before its hooks run, it reads every file of the worktree,
|
|
178
|
+
also what is nothing to preserve: a tracked file that is unchanged, and a
|
|
179
|
+
file under a work root a capability declared disposable
|
|
180
|
+
(`retirement.disposable.work`). A file without read
|
|
181
|
+
permission, or one over 2 GiB (which a single read cannot take), refuses
|
|
182
|
+
the retire with `E_WORK_INSPECTION_FAILED` before any retire hook runs,
|
|
183
|
+
with or without `--force`: no recovery was written and nothing was
|
|
184
|
+
deleted. Before, a retire whose snapshot held the home only did not read
|
|
185
|
+
the work and went through. The message is `could not read the worktree at
|
|
186
|
+
<work>: <reason>`: for a file without read permission the reason names the
|
|
187
|
+
file; for a file over 2 GiB it gives the size, and
|
|
188
|
+
`find <work> -type f -size +2147483647c` finds the file. To continue, move
|
|
189
|
+
the file out of the worktree or make it readable, then run
|
|
190
|
+
`oats retire <instance>` again. As for a socket or a FIFO that the new
|
|
191
|
+
read meets, the session of a launched instance has already been stopped by
|
|
192
|
+
then, the instance is not
|
|
193
|
+
retired, and its home is kept.
|
|
194
|
+
- **A worktree that cannot be proven unchanged is copied before the hooks,
|
|
195
|
+
and a retire of one that preserved only the home can now be refused.**
|
|
196
|
+
A worktree is not provable when it holds a repository (a `.git` entry of
|
|
197
|
+
any kind under it, a dangling symbolic link included), or when a read of
|
|
198
|
+
its Git state fails while `git status` works (for example
|
|
199
|
+
`info/attributes` or the stash's log cannot be read). Such a worktree
|
|
200
|
+
always has its work in the pre-hook snapshot and is copied again after
|
|
201
|
+
the hooks; with nothing to preserve it retires as before. The newly
|
|
202
|
+
refused case: a retire whose pre-hook snapshot would have held the home
|
|
203
|
+
only, whose worktree has a state read that fails or a `.git` entry under
|
|
204
|
+
it that OATS cannot read as a repository, and whose copy cannot be made
|
|
205
|
+
either. It refuses with `E_WORK_PRESERVATION_FAILED` before any
|
|
206
|
+
retire hook runs: no recovery was written and nothing was deleted; the
|
|
207
|
+
session of a launched instance has already been stopped, the instance is
|
|
208
|
+
not retired, and its home and work are kept. Before, that retire went
|
|
209
|
+
through and the work was never read. A retire hook can leave the worktree
|
|
210
|
+
in that state too: the work is then copied under `after-hooks/`, also when
|
|
211
|
+
the pre-hook snapshot held the home only, and when that copy cannot be
|
|
212
|
+
made the retire refuses with `E_WORK_PRESERVATION_FAILED` after the hooks
|
|
213
|
+
have run, with the home, the work and the recovery written before the
|
|
214
|
+
hooks kept. Before, that retire went through as well.
|
|
215
|
+
- **A retire refused by the copy made before its hooks says how far it
|
|
216
|
+
got.** The message of every `E_WORK_PRESERVATION_FAILED` raised by that
|
|
217
|
+
copy, whatever its cause (a recovery that could not be verified, an entry
|
|
218
|
+
that cannot be copied), now ends with: no retire hook has run, no recovery
|
|
219
|
+
was written and nothing was deleted, the instance is not retired, its home
|
|
220
|
+
and work are kept, and either "its session has been stopped" or "this
|
|
221
|
+
retire stopped no session". `--json` keeps the code and `details`; the
|
|
222
|
+
`message` field is longer. A refusal after the hooks is unchanged.
|
|
223
|
+
- **A retire that already refused an entry it cannot read now refuses it
|
|
224
|
+
with another error code.** A worktree that the retire copies before its
|
|
225
|
+
hooks (it has work to preserve) and that holds an entry that is not a
|
|
226
|
+
file, a directory or a symbolic link was refused with
|
|
227
|
+
`E_WORK_PRESERVATION_FAILED`, when the copy met the entry. It is now
|
|
228
|
+
refused earlier, when the worktree is read, with
|
|
229
|
+
`E_WORK_INSPECTION_FAILED`; the message names the entry's path and says
|
|
230
|
+
what to do. A worktree that cannot be proven unchanged is copied without
|
|
231
|
+
that read, and keeps `E_WORK_PRESERVATION_FAILED`. The home is kept in
|
|
232
|
+
both.
|
|
233
|
+
- **`oats retire --json` no longer emits `workRecoveries`.** The receipt has
|
|
234
|
+
one `workRecovery`, which keeps `path`, `classes`, `bytes`, `outputs` and
|
|
235
|
+
`repoCopy` and gains `home` (what was copied from the home), `notCopied`
|
|
236
|
+
and `afterHooks: {home, work}` (which parts are under `after-hooks/`).
|
|
237
|
+
A reader that consumes the receipt should read `workRecovery`, and keep
|
|
238
|
+
accepting `workRecoveries[]` from an older kernel on a server. The
|
|
239
|
+
Desktop's reader of the receipt is unchanged. See
|
|
240
|
+
[the CLI API](../desktop-cli-api.md#retire).
|
|
241
|
+
- **`oats retire <instance> --plan` says what a retire would copy.** Two
|
|
242
|
+
`notes` are added: where changed home files are copied, with the declared
|
|
243
|
+
entries that are not copied, and whether the work is copied too (worktree
|
|
244
|
+
and directory modes). No other key changes, and `planRevision` is
|
|
245
|
+
unaffected.
|
|
246
|
+
- **A forced removal past incomplete cleanup no longer leaves a copy of
|
|
247
|
+
declared state in recovery.** `oats retire <instance> --force` on a home
|
|
248
|
+
whose cleanup cannot finish removes the home after preserving its work.
|
|
249
|
+
Entries declared in `retirement.disposable.home` go with the home; the
|
|
250
|
+
recovery holds no copy of them.
|
|
251
|
+
- **A malformed `retirement` is refused where the manifest is read.** Member
|
|
252
|
+
discovery (`E_WORKSPACE_SCHEMA`), package manifests (`E_PACKAGE_MANIFEST`)
|
|
253
|
+
and the kernel loader refuse the same manifest with the same JSON pointer.
|
|
254
|
+
`retirement` must contain a `disposable` map with only `home` and `work`,
|
|
255
|
+
each an array of strings. A `home` entry must be one hidden top-level name
|
|
256
|
+
(`^\.[A-Za-z0-9_][A-Za-z0-9._-]*$`) or a prefix
|
|
257
|
+
(`^\.[A-Za-z0-9_][A-Za-z0-9._-]*-\*$`). Refused: anything else (`notes`,
|
|
258
|
+
`.aw/keys`, `.a*`); an exact name that is `.oats`, `.agents` or `.claude`,
|
|
259
|
+
or that starts with `.oats-events`, `.oats-stop`, `.oats-restart`,
|
|
260
|
+
`.oats-rollback`, `.oats-agents-md`, `.oats-start` or `.oats-attachments`
|
|
261
|
+
(the entries the kernel itself writes in a home); and a prefix that starts
|
|
262
|
+
with `.oats-`. An exact name such as `.oats-aweb` is allowed. The published
|
|
263
|
+
[manifest schema](../capability-manifest.schema.json) states the same
|
|
264
|
+
rules.
|
|
265
|
+
|
|
266
|
+
**This is stricter than before for `home`.** Before this release `home`
|
|
267
|
+
entries were not checked against a grammar; an entry outside the grammar
|
|
268
|
+
now refuses the whole manifest. For a home whose module copy
|
|
269
|
+
(`<home>/.oats/modules/<capability>/oats.json`) carries such an entry, the
|
|
270
|
+
commands that load the home's module manifests stop: starting or
|
|
271
|
+
restarting its session, and capability commands run from the home.
|
|
272
|
+
`oats inspect` reports it as a `module-manifest-invalid` problem instead of
|
|
273
|
+
failing. Retiring such a home still works, because retire runs the hooks
|
|
274
|
+
recorded in `instance.json` at spawn and does not load the manifests. No
|
|
275
|
+
capability shipped in this repository declares `retirement`.
|
|
276
|
+
|
|
277
|
+
- **A pane's environment is its tmux session's, `PATH` included, whoever
|
|
278
|
+
opens its window** ([#616](https://github.com/awebai/oats/issues/616)).
|
|
279
|
+
The tmux client that creates an agent's window, or respawns its pane in a
|
|
280
|
+
restart in place, now runs with only `LANG`, `LC_ALL` and `LC_CTYPE`, for
|
|
281
|
+
every creator: an instance, the Desktop, an operator's shell. tmux then gives
|
|
282
|
+
the pane its session's `PATH`, or the server's global one, instead of the
|
|
283
|
+
creating process's. Before, a pane's `PATH` was the `PATH` of each `oats
|
|
284
|
+
spawn` or `oats session start`, while the rest of its environment was the
|
|
285
|
+
server's: a version-manager wrapper (an Omarchy `~/.local/bin/claude`,
|
|
286
|
+
which runs `mise x claude -- claude`) then saw a `PATH` without the
|
|
287
|
+
variables that interpret it, resolved its own name again and looped. A
|
|
288
|
+
`PATH` and its companion state now come from one environment. OATS does not
|
|
289
|
+
certify that environment: an existing or external server, a session's own
|
|
290
|
+
`PATH` and a launch configuration's `PATH` are used as they are. The tmux
|
|
291
|
+
client is run by the absolute `tmux` the creating process's own `PATH`
|
|
292
|
+
finds. See [execution targets](../execution-targets.md#the-servers-start-environment).
|
|
293
|
+
- **The harness is looked up on the `PATH` its pane runs it with**
|
|
294
|
+
([#616](https://github.com/awebai/oats/issues/616)). A spawn or a start
|
|
295
|
+
that looks its executable up by name (the harness's, or a launch
|
|
296
|
+
configuration's bare `executable`) uses, in this order: a declared absolute
|
|
297
|
+
`executable` as it is; the launch configuration's own `PATH`; otherwise the
|
|
298
|
+
session's or the server's `PATH`, never the creating process's. It looks
|
|
299
|
+
once before anything is created, against the `PATH` it expects, and again
|
|
300
|
+
on the session it actually gets (another creator may have started the
|
|
301
|
+
server, a session may override `PATH`): found elsewhere, that executable is
|
|
302
|
+
launched and recorded; not found, the spawn is rolled back. With no server
|
|
303
|
+
running, a start under a new selection looks it up on the `PATH` of the
|
|
304
|
+
environment the server will be created with. A start that reuses the
|
|
305
|
+
home's recorded pane looks on that pane's own session, wherever it is
|
|
306
|
+
recorded, never on the OATS server. A session that
|
|
307
|
+
clears `PATH`, a `PATH` the strict reader cannot read and a server with no
|
|
308
|
+
`PATH` are refused, never substituted. The refusal is
|
|
309
|
+
`E_HARNESS_UNAVAILABLE` (`E_LAUNCH_EXECUTABLE` for a configuration's
|
|
310
|
+
declared name); its message names where it looked and the remedies
|
|
311
|
+
(declare `executable`, set `PATH` in the launch configuration, or start the
|
|
312
|
+
server from your own shell) and prints no `PATH`. A recorded executable,
|
|
313
|
+
reused by a plain start, is not looked up again.
|
|
314
|
+
- **When OATS starts the `oats` tmux server, it gives it your login
|
|
315
|
+
environment** ([#616](https://github.com/awebai/oats/issues/616)). Whoever
|
|
316
|
+
runs the command that starts it (an instance, the Desktop, an operator's
|
|
317
|
+
shell, a schedule runner), OATS runs your login shell from the password
|
|
318
|
+
database (bash, zsh or fish) as `-l -i -c` from a fixed seed (your `HOME`,
|
|
319
|
+
`USER`, `LOGNAME` and `SHELL`, a minimal `PATH`, `TERM=dumb`, the creator's
|
|
320
|
+
locale, and the session variables `SSH_AUTH_SOCK`, `DISPLAY`,
|
|
321
|
+
`WAYLAND_DISPLAY`, `XDG_RUNTIME_DIR` and `DBUS_SESSION_BUS_ADDRESS` from
|
|
322
|
+
the creator when it is no instance, the server an instance's home records,
|
|
323
|
+
or your user session), and starts the server with the environment that
|
|
324
|
+
shell sets up. Nothing else of the creator, so no agent identity,
|
|
325
|
+
credential or harness variable, reaches the server, except your OATS
|
|
326
|
+
configuration: the `OATS_` and `PI_AGENTS_` variables that are not the
|
|
327
|
+
kernel's (`OATS_HOME_DIR`, `OATS_TMUX_SESSION`, …) stay the creator's (an
|
|
328
|
+
instance's: its recorded server's), as they were before. The functions
|
|
329
|
+
bash exports (`BASH_FUNC_<name>%%`) are dropped. The login shell is run
|
|
330
|
+
only for a process whose `HOME` is your home directory; one that set
|
|
331
|
+
another `HOME` takes the fallback. The answer comes as one frame on the
|
|
332
|
+
shell's stdout, marked with a random value made for each reading (never a
|
|
333
|
+
file, and not a descriptor of its own, which bash 5.3 started `-l -i` closes
|
|
334
|
+
before the emitter runs); everything else the shell prints is discarded,
|
|
335
|
+
and a missing, cut or repeated frame is no answer; it is accepted only
|
|
336
|
+
whole (exit 0, at most 1 MiB, one JSON object of text, `HOME` and `PATH`
|
|
337
|
+
present) and never evaluated; the read is bounded at 5 s and the
|
|
338
|
+
shell's process group is killed at the deadline. Before, the server got the
|
|
339
|
+
creating process's environment (an instance: a copy of its recorded
|
|
340
|
+
server's). See [execution targets](../execution-targets.md#the-servers-start-environment).
|
|
341
|
+
|
|
342
|
+
## Fixed
|
|
343
|
+
|
|
344
|
+
- **No classic scrollbars in the Desktop on Linux.** Chromium on Linux drew a
|
|
345
|
+
classic scrollbar, arrows included, under the terminal tabs and beside a
|
|
346
|
+
terminal. The tab strips still scroll (the wheel, and to show the selected
|
|
347
|
+
tab) without a scrollbar, like the other tab bars, and a terminal's pane no
|
|
348
|
+
longer scrolls: the terminal scrolls itself. macOS showed neither.
|
|
349
|
+
- **A copy keeps the target of a symbolic link as its bytes.** A recovery,
|
|
350
|
+
and every other tree the kernel copies (a skill at spawn), recreates a
|
|
351
|
+
symbolic link whose target is not valid UTF-8 with the target's bytes.
|
|
352
|
+
Before, each such byte became a replacement character, and the link in the
|
|
353
|
+
copy named another path. A target that is valid UTF-8 is recreated as
|
|
354
|
+
before.
|
|
355
|
+
|
|
356
|
+
## Notes
|
|
357
|
+
|
|
358
|
+
- **No manual step for the `oats` server any more.** 0.41.0 asked you to
|
|
359
|
+
start it from your own shell before anything else did. Now, when OATS
|
|
360
|
+
starts it (after a reboot, or when its last session ends), it reads your
|
|
361
|
+
login environment itself. When that cannot be read, OATS says so in one
|
|
362
|
+
line on stderr, with no value, and falls back: a command run outside every
|
|
363
|
+
instance uses its own environment, as before; an instance uses a copy of
|
|
364
|
+
the server its home records, or is refused. A fallback is degraded: it is
|
|
365
|
+
not a login environment and does not prove a working agent socket, and it
|
|
366
|
+
passes the creator's environment on as it is, so a creator whose `PATH`
|
|
367
|
+
names a version manager's directories without the variables that interpret
|
|
368
|
+
them can still make a wrapper such as Omarchy's loop. Only a login
|
|
369
|
+
environment that OATS read prevents that.
|
|
370
|
+
- **A server you or a service started is used as it is.** A server you start
|
|
371
|
+
yourself (`tmux -L oats new-session …`) keeps your shell's environment; one
|
|
372
|
+
a service manager starts (`systemd-run`, a unit, launchd) gets only that
|
|
373
|
+
manager's environment, and OATS does not change or certify it. A server
|
|
374
|
+
that is already running is not restarted: the login environment applies the
|
|
375
|
+
next time OATS starts the server.
|
|
376
|
+
- **The pane rule applies to every window created after the upgrade, on
|
|
377
|
+
whatever server is running, for every creator: instances, the Desktop and
|
|
378
|
+
an operator's shell.** No pane is killed or migrated, and a running agent
|
|
379
|
+
keeps its environment. This is a behaviour change: a new pane's `PATH` is
|
|
380
|
+
now the server's (or its session's), no longer the creating process's, and
|
|
381
|
+
a harness found only on the creating process's `PATH` is refused with
|
|
382
|
+
`E_HARNESS_UNAVAILABLE`. On a server a service manager started
|
|
383
|
+
(`systemd-run`, a unit, launchd), whose `PATH` is only that manager's, a
|
|
384
|
+
spawn or start by bare harness name, an instance's included, is refused
|
|
385
|
+
until you act. To give the panes another `PATH`, set it on the server
|
|
386
|
+
(`tmux -L oats set-environment -g PATH "$PATH"`: windows created from then
|
|
387
|
+
on take it), declare the executable or a `PATH` in the launch
|
|
388
|
+
configuration, or let OATS start the server (it then gets your login
|
|
389
|
+
environment) or start it from your own shell.
|
|
390
|
+
|
|
391
|
+
- **Instances spawned before the upgrade retire with one copy, and still
|
|
392
|
+
copy provider state.** The declaration is recorded at spawn in the home's
|
|
393
|
+
retirement baseline, and retire reads it from there only. A home spawned
|
|
394
|
+
before its capability declared the entries gains no exclusion from a
|
|
395
|
+
package update: its home is copied whole, once. An instance spawned after
|
|
396
|
+
the capability declares them leaves them out.
|
|
397
|
+
- Existing recovery directories are not read, listed or deleted by this
|
|
398
|
+
release.
|