@awebai/oats 0.41.0 → 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.
@@ -134,33 +134,183 @@ options and environment, are left exactly as they were.
134
134
  #### The environment of the server and its panes
135
135
 
136
136
  A tmux server keeps the environment of the process that started it, as its
137
- global environment. A pane gets that, plus its session's environment (the
137
+ global environment. A pane gets its session's environment over that (the
138
138
  variables tmux's `update-environment` names, taken from whoever created the
139
- session), plus what the launch command sets (`OATS_INSTANCE`,
140
- `OATS_INSTANCE_HOME`, the capabilities' and the launch configuration's
141
- variables). One variable is different: **a pane's `PATH` is the `PATH` of the
142
- process that creates its window**, that is of each `oats spawn` or `oats
143
- session start`, with the home's own `oats` shim put first by the launch
144
- command. So:
145
-
146
- - `PATH` follows the process that runs each spawn or start, not whoever
147
- started the server.
139
+ session, and anything set on the session with `set-environment -t`), plus
140
+ what the launch command sets (`OATS_INSTANCE`, `OATS_INSTANCE_HOME`, the
141
+ capabilities' and the launch configuration's variables). **A pane's
142
+ environment is its session's, `PATH` included, whoever creates its window**:
143
+ an instance, the Desktop or an operator's shell. The tmux client that creates
144
+ the window (or respawns the pane, in a restart in place) runs with only
145
+ `LANG`, `LC_ALL` and `LC_CTYPE`, and tmux takes a client's `PATH` only when
146
+ the client has one. The launch command then puts the home's own `oats` shim
147
+ first. So:
148
+
149
+ - A pane's `PATH` is its session's `PATH` when the session has one, otherwise
150
+ the server's global `PATH`. It no longer follows the process that runs each
151
+ spawn or start (it did before 0.42.0).
152
+ - A `PATH` and the variables that interpret it (a version manager's
153
+ bookkeeping, such as mise's `__MISE_DIFF`, or nvm's) therefore come from
154
+ one environment. OATS no longer overlays a creator's `PATH` onto another
155
+ environment's companion state. It does not make an existing or external
156
+ server's environment, a session override or a launch configuration's
157
+ `PATH` coherent: it uses them as they are.
148
158
  - The rest of the ambient environment follows the server's first creator,
149
159
  and a server that already runs keeps what it started with. Creating a
150
160
  session or a window on it changes nothing global.
151
161
 
162
+ **The harness is looked up where its pane looks it up.** A launch whose
163
+ executable is a bare name (the harness's own, or a launch configuration's
164
+ `executable` without a `/`) looks it up on the `PATH` the pane will run it
165
+ with, by this precedence:
166
+
167
+ 1. a declared absolute `executable` is used as it is, with no lookup (a
168
+ relative one is resolved against the deployment directory, as before);
169
+ 2. a `PATH` set in the launch configuration's `env` (a literal, or a
170
+ `fromEnv` reference resolved on this host) is what the pane's command
171
+ line applies, so the lookup uses it;
172
+ 3. otherwise the session's or the server's `PATH`, read with the strict
173
+ reader below.
174
+
175
+ The lookup happens twice. Before anything is created, against the `PATH` the
176
+ pane is expected to have: for a start that reuses the home's recorded pane
177
+ (running, or left as a shell), that pane's own session on its recorded
178
+ server, whatever the OATS server holds and whether one runs; otherwise the
179
+ session's or the server's when the server runs, otherwise the `PATH` of the
180
+ environment the server is created with. And again
181
+ on the session OATS actually gets, whoever created it and with whatever
182
+ environment (another creator that won the race to create the server, a
183
+ session whose environment overrides `PATH`): found elsewhere, the launch runs
184
+ and records that executable; not there, the spawn is refused and rolled back
185
+ as any failed launch is, and a start is refused before it launches anything.
186
+ For a start the first check comes before a restart stops its harness; the
187
+ second, on the session OATS gets, comes after that session (and, when none
188
+ ran, the server) was created, and for a restart whose window went away
189
+ during its stop, after that stop. A session a refused start created is left
190
+ in place. A start under a new selection with no server running reads the
191
+ environment the server will be created with (your login environment, or its
192
+ fallback, below) for its first check, never this process's `PATH`. A recorded executable (a start that reuses the home's launch) is
193
+ kept as it is: it then runs with its pane's environment, but it is not looked
194
+ up again. A `--no-launch` spawn, and a preview with no server running, look
195
+ the harness up on the process's own `PATH`, as before.
196
+
197
+ A session's `PATH` can be in four states, and the lookup tells them apart:
198
+ no entry (the server's `PATH` is used), a value, even an empty one (used as
199
+ it is), an explicit clear (`unset PATH;` in `show-environment -s`: tmux gives
200
+ the pane no `PATH` at all), or a value the strict reader leaves out. The last
201
+ two, and a server with no `PATH` or one that cannot be read, are refused: OATS
202
+ never substitutes the server's `PATH`, the caller's or a default for them.
203
+
204
+ A harness that is not on that `PATH` is refused with `E_HARNESS_UNAVAILABLE`
205
+ (a configuration's declared bare name with `E_LAUNCH_EXECUTABLE`). The message
206
+ names where it looked (`the PATH of tmux session <name>`, `the global PATH of
207
+ the OATS tmux server (<socket>)`, or the launch configuration's) and the
208
+ remedies: declare the executable's absolute path in the launch configuration,
209
+ set `PATH` in the launch configuration, or start the server from your own
210
+ shell. It never prints a `PATH`'s contents.
211
+
152
212
  Who starts the `oats` server, after an install, a reboot or its last session
153
- ending, decides that ambient environment:
213
+ ending, decides that ambient environment.
214
+
215
+ **When OATS starts the server, it gives it your login environment.** The
216
+ `new-session` that creates an agents' session is the only OATS call that
217
+ starts the server. When no server answered before it, whoever runs it (a spawn
218
+ or a start from an instance, the Desktop, an operator's shell, a schedule
219
+ runner or a trigger), OATS reads the environment your own login shell sets up
220
+ and starts the server with that:
221
+
222
+ - **Only in your own session.** The login shell is run only for a process
223
+ whose `HOME` is your home directory in the password database. A process
224
+ that set another `HOME` (a sandbox, a test fixture, an account switch that
225
+ kept the caller's environment) is not taken to run in your login session:
226
+ its reading fails and the fallback below applies.
227
+ - **The shell** is your login shell from the password database (not the
228
+ creator's `SHELL`): bash, zsh or fish, run as `-l -i -c` from your home
229
+ directory, so your profile and rc files run as they do in a terminal (a
230
+ version manager's activation, an agent socket an rc exports).
231
+ - **It starts from a fixed seed**, nothing else of the creator: `HOME`,
232
+ `USER`, `LOGNAME` and `SHELL` from the password database, `PATH` as
233
+ `/usr/bin:/bin:/usr/sbin:/sbin`, `TERM=dumb`, the creator's `LANG`,
234
+ `LC_ALL` and `LC_CTYPE`, and the session variables `SSH_AUTH_SOCK`,
235
+ `DISPLAY`, `WAYLAND_DISPLAY`, `XDG_RUNTIME_DIR` and
236
+ `DBUS_SESSION_BUS_ADDRESS`. Each of those comes from the first source that
237
+ has it, an empty value counting as present: the creator, when it is not an
238
+ instance; for an instance, the global environment of the server its home
239
+ records; then your user session, read as data (`systemctl --user
240
+ show-environment` on Linux, decoding systemd's `$'…'` values; `launchctl
241
+ getenv` on macOS). A user session that cannot be read leaves those names to
242
+ your login shell, and OATS says so on stderr, without values. On macOS the
243
+ agent socket launchd gives the apps and terminals you open is not a
244
+ `launchctl` variable (it is the `SSH_AUTH_SOCK` key of the
245
+ `com.openssh.ssh-agent` job), so `launchctl getenv` usually has none: a
246
+ server OATS starts there has your agent socket when the process that starts
247
+ it has one (the Desktop opened from the Finder or the Dock, a terminal), or
248
+ when your login shell sets it. The tool runs
249
+ from `/usr/bin:/bin:/usr/sbin:/sbin` with your own `HOME`, `USER`,
250
+ `LOGNAME` and, on Linux, your runtime directory (`/run/user/<uid>`), never
251
+ with the creator's environment. No `OATS_`,
252
+ `AWEB_`, harness or credential variable of the creator is in the seed.
253
+ - **What your setup produces wins**: the shell's answer is final, including
254
+ what its rc files set, except the functions bash exports
255
+ (`BASH_FUNC_<name>%%`, from a profile's `export -f`): they are code every
256
+ bash in a pane would import, and OATS drops them. OATS then removes the
257
+ kernel's names and the instance-identity names, as below, and keeps your
258
+ OATS configuration as the creator has it: every `OATS_` and `PI_AGENTS_`
259
+ variable that is not a kernel name (`OATS_HOME_DIR`, `OATS_TMUX_SESSION`,
260
+ `OATS_PACKAGE_CATALOG`, …) is the creator's, over what your rc files set;
261
+ for an instance, the one the server its home records has, never its own. The server is still reached with the
262
+ creator's `TMUX_TMPDIR`.
263
+ - **The answer is one frame on the shell's stdout**: a small emitter run by
264
+ the shell writes the environment as JSON between a start and an end
265
+ delimiter that carry a random value made for this reading only. Exactly one
266
+ complete frame of that reading, start before end, is the answer; anything
267
+ else the shell prints (a banner, a prompt, text that looks like an
268
+ assignment, like JSON or like a frame of another reading) is discarded, and
269
+ a missing, cut, repeated or misordered frame is no answer. The shell's
270
+ errors are not read, and no value is written to a file, an argument or a
271
+ log. It is accepted only whole: exit status 0, at most 1 MiB for all the
272
+ shell printed, one JSON object whose values are all text, plain
273
+ identifiers as names, `HOME` and `PATH` present. Nothing in it is
274
+ evaluated. Not a descriptor of its own: bash 5.3, started `-l -i`, marks
275
+ the descriptors it inherits from 3 to 19 close-on-exec, so an answer there
276
+ never reaches the emitter. The random value keeps accidental output apart;
277
+ it does not guard against your own start-up files, which can also send the
278
+ shell's stdout elsewhere (then there is no answer, and the fallback
279
+ below applies).
280
+ - **It is bounded at 5 s.** The shell runs in its own process group; at the
281
+ deadline that group, and only it, is killed, and the read ends even when a
282
+ descendant still holds the shell's stdout. A descendant that put itself in its own
283
+ session or group (a daemon an rc starts) is outside that group, and OATS
284
+ does not promise to end it.
285
+ - **When it cannot be read** (a process whose `HOME` is not yours, a timeout, a non-zero exit, a partial,
286
+ oversized or malformed answer, no `HOME` or `PATH`, a shell that is not
287
+ bash, zsh or fish or cannot run), OATS prints one line on stderr saying
288
+ why and what it used instead, never a value, and `--json` output stays one
289
+ valid envelope. A creator outside every instance falls back to its own
290
+ environment without the kernel's names (the behaviour before 0.42.0); an
291
+ instance falls back to a copy of the server its home records, as below, or
292
+ is refused, never to its own environment. A fallback is degraded: it is
293
+ neither a login environment nor proof of a working agent socket, and it
294
+ passes on whatever the creator's environment holds. A creator whose `PATH`
295
+ names a version manager's directories without the variables that interpret
296
+ them (a process started by a service manager, an agent's shell) gives the
297
+ server that same mismatch, and a version-manager wrapper can then resolve
298
+ its own name again and loop, as in #616. OATS prevents that only when it
299
+ reads your login environment.
300
+
301
+ A server OATS did not start keeps what its starter gave it:
154
302
 
155
303
  | Started by | The server's environment |
156
304
  |---|---|
157
- | an operator's shell | that shell's |
158
- | the Desktop | your environment as the Desktop got it, without what the Desktop or its packaging added to its own, and with the login-shell `PATH` it puts in front ([desktop.md](desktop.md)) |
159
- | a schedule runner or a trigger | the service's |
160
- | an OATS instance that creates an agents' session | the global environment of the tmux server that instance's home records, reduced as described below; not the instance's own |
305
+ | OATS (any creator; see above) | your login environment, or the creator's fallback |
306
+ | you, from your own shell (`tmux -L oats new-session …`) | that shell's |
307
+ | a service manager (`systemd-run`, a unit, launchd) | only that manager's environment |
161
308
 
309
+ OATS uses an existing server as it is and does not certify its environment.
162
310
  The first creator that succeeds determines it; when two start it at the same
163
- moment, tmux starts one server and nothing says which of the two it is.
311
+ moment, tmux starts one server and nothing says which of the two it is. A
312
+ server that exits between OATS's lookup and its `new-session` is started as
313
+ any server OATS starts: with your login environment, or its fallback.
164
314
 
165
315
  **What OATS removes, for every creator.** The names the kernel itself sets
166
316
  never go into the environment OATS creates an agents' session or window
@@ -174,13 +324,16 @@ operation, a retire or a trigger is given (`OATS_CAPABILITY`, `OATS_LAYER`,
174
324
  `OATS_OPERATION`, `OATS_REPO`, `OATS_BRANCH`, `OATS_WORK`, `OATS_KIND`,
175
325
  `OATS_TASK`, `OATS_HARNESS`, `OATS_PREVIOUS_HARNESS`, `OATS_RUNTIME`,
176
326
  `OATS_PREVIOUS_RUNTIME`, `OATS_LAUNCH_PREVIEW`, `OATS_RETIRE_INTENT`,
177
- `OATS_TRIGGER_EVENT_FILE`, `OATS_TEAM_NAME`, `OATS_TEAM_SCOPE`,
327
+ `OATS_TRIGGER_EVENT_FILE`, `OATS_TEST_LOGIN_SHELL` (a test seam that replaces
328
+ the login shell, honoured only for a process whose `HOME` is not your home
329
+ directory; a launch configuration cannot set it), `OATS_TEAM_NAME`, `OATS_TEAM_SCOPE`,
178
330
  `OATS_TEAM_ID`, `OATS_TEAM_LABEL`, `OATS_TEAM_LABELS`, `OATS_TEAMS`,
179
331
  `OATS_TEAMS_SOURCE`, `OATS_DEFAULT_TEAM`, `OATS_DEFAULT_TEAM_ID`,
180
332
  `OATS_DEFAULT_TEAM_FROM`, `OATS_WORKSPACE_NAME`, `OATS_WORKSPACE_KEY`); and
181
333
  every launch reference (`OATS_LAUNCH_REF_<NAME>`) with the `<NAME>` it stands
182
334
  for. Other `OATS_` variables you export (`OATS_HOME_DIR`,
183
- `OATS_TMUX_SESSION`) are yours and stay. An agent's plain `oats` finds its
335
+ `OATS_TMUX_SESSION`) are yours and stay, also when OATS starts the server
336
+ with your login environment (above). An agent's plain `oats` finds its
184
337
  deployment from its home.
185
338
 
186
339
  **An instance creates an agents' session or window without its own
@@ -193,7 +346,8 @@ spawns or starts another instance, a capability hook that spawns or starts
193
346
  (a hook runs with its instance's identity, also when a person ran the command
194
347
  that triggered it), and `oats schedule run-now` typed inside an instance.
195
348
 
196
- - When such a process has to create the tmux session, OATS reads the global
349
+ - When such a process has to create the tmux session on a server that runs,
350
+ or its login environment could not be read, OATS reads the global
197
351
  environment of the server its home records (`tmux -S <recorded socket>
198
352
  show-environment -g -s`, the endpoint of the home's receipt, checked
199
353
  against `instance.json` as every session command checks it) and creates
@@ -218,20 +372,19 @@ that triggered it), and `oats schedule run-now` typed inside an instance.
218
372
  `XDG_CONFIG_HOME`, `PATH`, `SHELL`), the creation is refused instead, with
219
373
  the same remedy as below.
220
374
  - When it creates a window in a session that exists, the tmux client that
221
- creates it runs with `PATH`, without any instance's `oats` shim directory,
222
- and with `LANG`, `LC_ALL` and `LC_CTYPE`, which that client needs to start
223
- on a host whose only UTF-8 locale is the one they name. tmux hands a pane
224
- the `PATH` of that client and nothing else of it: the three locale names
225
- stay with the client, and the pane, the session and the server keep the
226
- values they had. Nothing else of the instance travels. When the instance
227
- has no `PATH` to give (none is set, or only `oats` shim directories were
228
- in it), none is passed: the pane then takes no `PATH` from the client. A
229
- restart that reuses the pane the home already has runs its tmux client
230
- with the same environment; where and whether the pane is reused does not
231
- change.
375
+ creates it runs with `LANG`, `LC_ALL` and `LC_CTYPE` only, which that client
376
+ needs to start on a host whose only UTF-8 locale is the one they name, as
377
+ every creator's does. tmux hands a pane none of them: the pane, the session
378
+ and the server keep the values they had, and the pane's `PATH` is the
379
+ session's or the server's. Nothing of the instance travels, its `PATH`
380
+ included. A restart that reuses the pane the home already has runs its tmux
381
+ client with the same environment; where and whether the pane is reused does
382
+ not change.
232
383
  - **It is refused** (`E_RUNTIME_ENDPOINT_UNKNOWN`; `oats spawn` reports it
233
384
  as `E_SPAWN_FAILED` with the same message) when the session does not exist
234
- on the `oats` server and there is no source to read: the home records no
385
+ on the `oats` server, its login environment is not what the server gets
386
+ (the server runs, or the login environment could not be read), and there is
387
+ no source to read: the home records no
235
388
  tmux server (it was never launched), its receipt cannot be used, or its
236
389
  recorded server cannot be reached or read. A process that carries an
237
390
  instance's identity (`OATS_INSTANCE`, `OATS_INSTANCE_HOME`, `OATS_HOME`,
@@ -277,42 +430,46 @@ that triggered it), and `oats schedule run-now` typed inside an instance.
277
430
  the environment it passes: its own `tmux`, its own `TMUX_TMPDIR`, and the
278
431
  socket the lookup returned when the server runs. Its own `tmux` is the one
279
432
  its own `PATH` finds, and the session is created by that program's full
280
- path. A process whose `PATH` holds no tmux cannot read the server at all:
433
+ path, as is every window client (it has no `PATH` to look one up with). A
434
+ process whose `PATH` holds no tmux cannot read the server at all:
281
435
  it is refused (`E_RUNTIME_ENDPOINT_UNKNOWN`) whether or not the session
282
436
  exists, and told to run the command with a `PATH` that holds tmux. A
283
- process whose `PATH` is not set is refused only when it has to create the
284
- session (tmux used to be found by the system's default search then); with
285
- the session already there, it is not refused. What the server process
437
+ process whose `PATH` is not set has no tmux to run by its full path: it
438
+ cannot create a session, nor open or respawn a window (the window is
439
+ refused). Only finding a session that is already there is not refused. What the server process
286
440
  gets (`HOME` and so which configuration loads, `PATH`, everything else)
287
441
  comes from the passed environment alone. The global environment of the
288
442
  server the session is created on is never read for this and never changed;
289
443
  the one that is read is the recorded server's, as above.
290
444
 
291
445
  A process that neither sign identifies as an instance is treated as any
292
- other creator: its environment, without the names above, is what it passes.
446
+ other creator: when it starts the server, your login environment (above);
447
+ otherwise, and as the fallback, its environment without the names above.
293
448
 
294
449
  By hand (`<session>` is the deployment's session name: `oats-agents` unless
295
450
  `session.tmuxSession` or `OATS_TMUX_SESSION` says otherwise):
296
451
 
297
452
  ```sh
298
- tmux -L oats new-session -d -s <session> -n hq # start it from your own shell, before anything else does
453
+ tmux -L oats new-session -d -s <session> -n hq # start it yourself: it keeps your shell's environment
299
454
  tmux -L oats show-environment -g # what environment the server has
300
455
  tmux -L oats show-environment -g LANG # one variable
301
456
  tmux -L oats set-environment -g LANG "$LANG" # repair a running server: windows created from now on inherit it
302
457
  tmux -L oats kill-server # replace it: ends EVERY session on that server
303
458
  ```
304
459
 
305
- - Start it yourself after an upgrade or a reboot, before agents, the Desktop
306
- or a schedule run. OATS finds a session you created and uses it: it looks
307
- the session up by its exact name and assumes nothing about its `hq`
308
- window.
460
+ - There is no manual step: when OATS starts the server, it gives it your
461
+ login environment. A server you start yourself keeps your shell's
462
+ environment, and one a service manager starts gets only that manager's.
463
+ OATS finds a session you created and uses it: it looks the session up by
464
+ its exact name and assumes nothing about its `hq` window.
309
465
  - `set-environment -g` is how a running server is repaired: it ends nothing,
310
- and panes that already run keep the environment they have. A pane's `PATH`
311
- is always that of the process that runs the spawn or the start, so setting
312
- `PATH` on the server changes nothing for OATS panes.
466
+ and panes that already run keep the environment they have. Windows created
467
+ from now on take it, `PATH` included (with no session override of it), and
468
+ their harness is looked up on that `PATH`.
313
469
  - `kill-server` ends every session on that server, every running agent
314
470
  included. It is for before agents are started, not for repairing a running
315
- host. The next start takes the environment of whoever starts it.
471
+ host. The next start gives the new server your login environment when OATS
472
+ starts it, or the environment of whoever else does.
316
473
  - `-L oats` reaches the server of the environment you type it in
317
474
  (`TMUX_TMPDIR`). For the server an instance is on, use the socket from its
318
475
  row in `oats status --json`: `tmux -S <socket> …`.
@@ -60,7 +60,9 @@ published to npm. Its developer docs are in
60
60
  | `operator-dispatch.mjs` | capability commands run from a deployment, and its module store |
61
61
  | `instance-*.mjs` | inspection, lifecycle, events and Git views of an instance |
62
62
  | `tmux-config.mjs`, `session-*.mjs` | the tmux session backend and terminal input |
63
- | `capability-contract.mjs`, `provider-binding.mjs` | manifest validation, the hook environment rules, the readiness wire |
63
+ | `login-environment.mjs` | the user's login environment a server OATS starts gets: the login shell's answer as one nonce-framed block on its stdout, bounded and accepted only whole ([execution targets](execution-targets.md#the-servers-start-environment)) |
64
+ | `capability-contract.mjs`, `provider-binding.mjs` | manifest validation (launch environment, hooks, `retirement`), the hook environment rules, the readiness wire |
65
+ | `retire-output.mjs` | the lines `oats retire` prints for preserved work, one function for the local and the remote path |
64
66
  | `servers.mjs` | routing commands to a registered server |
65
67
  | `harness-trust.mjs` | reading (never writing) Claude's and Codex's folder trust for a launch |
66
68
 
@@ -0,0 +1,7 @@
1
+ # OATS 0.41.1
2
+
3
+ ## Changed
4
+
5
+ - **OATS Desktop opens up to 200 terminals.** Desktop allowed 20 open terminals
6
+ across all its windows, and with many instances it refused the next one with
7
+ "Terminal limit reached. Close a terminal first." The limit is now 200.