@awebai/oats 0.31.0 → 0.32.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 +141 -36
- package/docs/configuration.md +65 -8
- package/docs/desktop-cli-api.md +121 -16
- package/docs/desktop.md +16 -0
- package/docs/implementation.md +58 -0
- package/docs/oats-local.schema.json +2 -1
- package/docs/plans/0.30-close-out.md +7 -4
- package/docs/release-notes/v0.32.0.md +137 -0
- package/lib/automations.mjs +5 -1
- package/lib/core.mjs +75 -29
- package/lib/instance-inspect.mjs +2 -1
- package/lib/instance-resolution.mjs +3 -3
- package/lib/local-inputs.mjs +46 -0
- package/lib/materialize.mjs +3 -2
- package/lib/packages.mjs +26 -6
- package/lib/remote.mjs +792 -36
- package/lib/resolve.mjs +23 -7
- package/lib/servers.mjs +7 -0
- package/lib/workspace.mjs +163 -56
- package/package.json +1 -1
package/docs/desktop-cli-api.md
CHANGED
|
@@ -38,7 +38,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
|
|
|
38
38
|
"instance-events-2","schedule-history","schedule-read-2","spawn-preview-2","spawn-idempotency","spawn-idempotency-2","spawn-apply-2",
|
|
39
39
|
"workspace-v2","instance-modules","spawn-provider-payload","served-identity","packages-no-approval","spawn-name","settings-origins",
|
|
40
40
|
"team-model-2","settings-declared","capabilities-private","layers-from","harness","package-souls","triggers","automations","desktop-facts","launch-preference",
|
|
41
|
-
"preview-composed-from"],
|
|
41
|
+
"preview-composed-from","observe-max-age"],
|
|
42
42
|
"automationsApi":1,"workspaceApi":2,"instanceGitApi":1,"spawnApplyApi":1,"soulsApi":2,"lifecycleApi":1,
|
|
43
43
|
"readinessApi":2,"spawnPreviewApi":2,"eventsApi":2,"scheduleHistoryApi":3,"scheduleApi":2,"operationsApi":2}
|
|
44
44
|
```
|
|
@@ -95,6 +95,7 @@ canonical (`github.com/<org>/<repo>`, or `local/<abs-path>`). Examples use
|
|
|
95
95
|
| `desktop-facts` | the facts under [Desktop facts](#desktop-facts-feature-desktop-facts-oats-0290) | |
|
|
96
96
|
| `launch-preference` | soul and local launch preferences; `launch`, `launchCurrent`, `launchFrom`; `--reselect-launch`; `key` on soul and agent rows ([Launch preferences](#soul-launch-preferences-feature-launch-preference-oats-0300)) | |
|
|
97
97
|
| `preview-composed-from` | `composedFrom` on preview `modules[]` ([Composition](#the-preview)) | |
|
|
98
|
+
| `observe-max-age` | `--max-age <s>` on the read verbs and their `observation` block ([Observation reuse](#observation-reuse-feature-observe-max-age-oats-0311)) | |
|
|
98
99
|
|
|
99
100
|
Payload-only integers, never in the probe: `onboardApi: 2`, `syncApi: 1`,
|
|
100
101
|
`workspaceStatusApi: 1`, `capabilitiesApi: 1`, the `oats souls` document's
|
|
@@ -181,6 +182,83 @@ an inherited `OATS_DEPLOYMENT` or `OATS_RESOLUTION` (`details.inherited`).
|
|
|
181
182
|
homes in `problems[]`: `legacy-captured-home {code, instances, homes,
|
|
182
183
|
message}` and `legacy-local-agents {code, dirs, instances, message}`.
|
|
183
184
|
|
|
185
|
+
<a id="observation-reuse-feature-observe-max-age-oats-0311"></a>
|
|
186
|
+
### Observation reuse (feature `observe-max-age`, OATS 0.32.0)
|
|
187
|
+
|
|
188
|
+
Every read asks each remote for its current head (`git ls-remote`). With
|
|
189
|
+
`--max-age <seconds>` a read verb reuses a head this machine observed at most
|
|
190
|
+
that many seconds ago instead, so a refresh right after another costs no
|
|
191
|
+
network round trip. Gate the flag on the feature: an older kernel may ignore
|
|
192
|
+
it and answer live, without the block.
|
|
193
|
+
|
|
194
|
+
```text
|
|
195
|
+
oats status | workspace status | souls | capabilities | inspect --soul|--home
|
|
196
|
+
| teams | soul teams <soul> … --max-age <seconds> --json
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
- **Values:** whole seconds, `0` to `86400`. `0` is live: it reuses nothing.
|
|
200
|
+
Anything else is `E_BAD_ARGS` (`--max-age needs a value: whole seconds from 0
|
|
201
|
+
to 86400`, `--max-age takes whole seconds from 0 to 86400, got "<v>"`).
|
|
202
|
+
- **The block:** with the flag (`0` included) the document gains one key,
|
|
203
|
+
`observation: {observedAt, reused, localRevision}`: in the result of an
|
|
204
|
+
envelope, at the top level of the roster (after `agents`). `observedAt` is the
|
|
205
|
+
OLDEST remote head the answer used, so the answer is at least that fresh
|
|
206
|
+
everywhere; `reused` is `true` when any head came from an earlier observation.
|
|
207
|
+
A command that read no remote head reports the time it started and
|
|
208
|
+
`reused: false`. Without the flag the key is absent and every document is
|
|
209
|
+
exactly as before.
|
|
210
|
+
- **`localRevision`:** 24 lowercase hex characters, opaque. It digests every
|
|
211
|
+
piece of local configuration the kernel read for this answer:
|
|
212
|
+
`oats-local.yaml` (and each closer `oats-local.yaml` it looked for and did
|
|
213
|
+
not find), `oats-lock.json`, an `OATS_PACKAGE_CATALOG` file, and the
|
|
214
|
+
automations snapshot. Only what the verb actually read counts. The same inputs
|
|
215
|
+
give the same revision; any byte change, or one of those files appearing or
|
|
216
|
+
disappearing, gives another. It names no path and no content. Different verbs
|
|
217
|
+
read different inputs (`workspace status` also reads the automations
|
|
218
|
+
snapshot; `inspect --home` reads no lock), so compare revisions of the same
|
|
219
|
+
verb and arguments only. Keep what you
|
|
220
|
+
hold (catalogs, inspect results) keyed on it: a different revision means the
|
|
221
|
+
deployment's configuration changed outside you (a `teams` edit, a sync, a
|
|
222
|
+
hand edit). A kernel upgrade shows through the probe (`oats version --json`),
|
|
223
|
+
not through `localRevision`: the bundled catalog is not an input. Instance
|
|
224
|
+
homes, member clones and tmux are not inputs either, because the answer
|
|
225
|
+
itself reports them.
|
|
226
|
+
- **What is reused:** only remote heads (the commit a branch or tag named),
|
|
227
|
+
never local state. Instances, `oats-local.yaml` and the lock are read afresh
|
|
228
|
+
by every command. A member's backlink (`oats-membership.yaml`) is read at
|
|
229
|
+
the member's observed head, which may be a reused one: a backlink removed
|
|
230
|
+
less than `<s>` seconds ago can still show the member `confirmed` under
|
|
231
|
+
`--max-age <s>`. Only the backlink's comparison with this workspace is made
|
|
232
|
+
afresh. A reused head's
|
|
233
|
+
`observedAt` (for example `workspace.observedAt` in `workspace status`) is
|
|
234
|
+
the time it was observed, not now.
|
|
235
|
+
- **When a head is not reused:** it is older than the flag allows (or dated
|
|
236
|
+
more than 5 s in the future); it was observed through a different URL
|
|
237
|
+
spelling of the same repository (ssh vs https) or for a different ref; its
|
|
238
|
+
commit can no longer be fetched. Each is observed live, as without the flag.
|
|
239
|
+
A live observation that fails is the usual error, never an older head.
|
|
240
|
+
- **Refusals:** every other command, every edit form (`teams add|remove|default`,
|
|
241
|
+
`soul teams --add|--remove|--default|--clear-default`) and any `--server`
|
|
242
|
+
invocation refuse the flag before reading or writing anything, with
|
|
243
|
+
`E_BAD_ARGS` "--max-age is not accepted by \`oats <form>\`: only the read
|
|
244
|
+
verbs reuse observations (status, workspace status, souls, capabilities,
|
|
245
|
+
inspect --soul|--home, and the read forms of teams and soul teams)" and,
|
|
246
|
+
with `--server`, "--max-age cannot be combined with --server: observation
|
|
247
|
+
reuse is local to this machine".
|
|
248
|
+
A capability command's argv (`oats <namespace> …`) is its provider's: the
|
|
249
|
+
kernel neither reads nor refuses `--max-age` there. The same holds for
|
|
250
|
+
`capture`, `recall`, `setup` and `experimental`, which parse their own argv:
|
|
251
|
+
`capture`, `setup` and `experimental` refuse it as an unknown argument (not
|
|
252
|
+
`E_BAD_ARGS`), and `recall` ignores unknown flags.
|
|
253
|
+
|
|
254
|
+
The observations are kept under the remote cache
|
|
255
|
+
(`$OATS_REMOTE_CACHE`, default `~/.cache/oats/remotes`), in `.observed/`,
|
|
256
|
+
beside the bounded parsed-read cache in `.parsed/`. An observation record
|
|
257
|
+
keeps a digest of the fetch URL, never the URL. A parsed entry keeps repository
|
|
258
|
+
content as committed (member refs included), and a value that carries a
|
|
259
|
+
credential-bearing URL (userinfo on http(s), or `user:password@` on any
|
|
260
|
+
scheme) is never written. Deleting either is always safe.
|
|
261
|
+
|
|
184
262
|
<a id="inspect-readiness-and-operation-run-on-the-workspace-model-operationsapi-2-soulsapi-2-readinessapi-2-oats-0260"></a>
|
|
185
263
|
## Inspect, readiness and operation run
|
|
186
264
|
|
|
@@ -214,7 +292,7 @@ A module's origin (`from`) is `{kind: "member", repoKey, commit}` or `{kind:
|
|
|
214
292
|
### `oats inspect`
|
|
215
293
|
|
|
216
294
|
```text
|
|
217
|
-
oats inspect (--home <abs> | --soul <name> [--dir <d>]) --json
|
|
295
|
+
oats inspect (--home <abs> | --soul <name> [--dir <d>]) [--max-age <s>] --json
|
|
218
296
|
```
|
|
219
297
|
|
|
220
298
|
An instance subject, abridged:
|
|
@@ -597,7 +675,7 @@ discovers the workspace over the network to check. Other errors: `E_USAGE`,
|
|
|
597
675
|
### `oats workspace status`
|
|
598
676
|
|
|
599
677
|
```text
|
|
600
|
-
oats workspace status [--dir <d>] --json
|
|
678
|
+
oats workspace status [--dir <d>] [--max-age <s>] --json
|
|
601
679
|
```
|
|
602
680
|
|
|
603
681
|
Read-only (it writes no lock):
|
|
@@ -646,8 +724,8 @@ Read-only (it writes no lock):
|
|
|
646
724
|
### `oats capabilities` and `oats souls`
|
|
647
725
|
|
|
648
726
|
```text
|
|
649
|
-
oats capabilities [--dir <d>] --json
|
|
650
|
-
oats souls [--dir <d>] --json
|
|
727
|
+
oats capabilities [--dir <d>] [--max-age <s>] --json
|
|
728
|
+
oats souls [--dir <d>] [--max-age <s>] --json
|
|
651
729
|
```
|
|
652
730
|
|
|
653
731
|
Every item of every confirmed member, the external souls, and the locked
|
|
@@ -835,7 +913,7 @@ soul subject), `""` when unknown.
|
|
|
835
913
|
### `oats teams`
|
|
836
914
|
|
|
837
915
|
```text
|
|
838
|
-
oats teams [--dir <d>] --json
|
|
916
|
+
oats teams [--dir <d>] [--max-age <s>] --json
|
|
839
917
|
oats teams add <label> --team <id> [--description <d>] --json
|
|
840
918
|
oats teams remove <label> --json
|
|
841
919
|
oats teams default <label> --json
|
|
@@ -874,6 +952,7 @@ oats teams default <label> --json
|
|
|
874
952
|
|
|
875
953
|
```text
|
|
876
954
|
oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--dir <d>] --json
|
|
955
|
+
oats soul teams <soul>|'*' [--dir <d>] [--max-age <s>] --json (the read form only)
|
|
877
956
|
```
|
|
878
957
|
|
|
879
958
|
```json
|
|
@@ -1135,6 +1214,12 @@ it to a temporary copy (`soulFetched: true`).
|
|
|
1135
1214
|
(absent when nothing sets it) are the resolved selection.
|
|
1136
1215
|
`backendStatus` is `{name, installed, started: false}`, `null` with
|
|
1137
1216
|
`--no-launch`. `executable` is the resolved harness binary.
|
|
1217
|
+
- `launchConfigDefault` (0.32, feature `launch-config-default`) is `true`
|
|
1218
|
+
when `launchConfig` is this host's default for the harness (its
|
|
1219
|
+
executable, args, env and `yolo` apply without being chosen), `false`
|
|
1220
|
+
otherwise. Show it, and `yolo`, whenever it is `true`. An explicit
|
|
1221
|
+
`--launch-config none` asks for the bare harness and bypasses the default;
|
|
1222
|
+
to run the host's default, omit `--launch-config`.
|
|
1138
1223
|
- `modelSource` is `"explicit"`, `"soul default"`, `"launch-config <name>"`,
|
|
1139
1224
|
`"native default"` or `"native default (explicit)"` (`--model
|
|
1140
1225
|
@native-default`). Omitting `--model` and asking for the native default are
|
|
@@ -1346,10 +1431,11 @@ the keyed-spawn fields `decision`, `spawnIdempotencyKey`, `spawnCompleted` and
|
|
|
1346
1431
|
### The roster (`oats status --json`)
|
|
1347
1432
|
|
|
1348
1433
|
```text
|
|
1349
|
-
oats status [--dir <d>] --json
|
|
1434
|
+
oats status [--dir <d>] [--max-age <s>] --json
|
|
1350
1435
|
```
|
|
1351
1436
|
|
|
1352
|
-
Not an envelope: `{root, agents, workspace?, problems?, warnings?}
|
|
1437
|
+
Not an envelope: `{root, agents, observation?, workspace?, problems?, warnings?}`
|
|
1438
|
+
(`observation` only with [`--max-age`](#observation-reuse-feature-observe-max-age-oats-0311)).
|
|
1353
1439
|
|
|
1354
1440
|
```json
|
|
1355
1441
|
{"root":"/w/agents",
|
|
@@ -1812,26 +1898,45 @@ accept `--server <id>`.
|
|
|
1812
1898
|
{"context":"/w","level":"/w","file":"/w/oats-local.yaml","selected":null,
|
|
1813
1899
|
"configurations":[{"name":"reviewers","harness":"claude","executable":null,"args":["--permission-mode","plan"],
|
|
1814
1900
|
"env":{"ANTHROPIC_API_KEY":{"fromEnv":"REVIEW_KEY"},"REVIEW_MODE":{"redacted":true}},
|
|
1815
|
-
"model":"opus","yolo":null,"source":"/w/oats-local.yaml","shadows":[]}]}
|
|
1901
|
+
"model":"opus","yolo":null,"default":false,"source":"/w/oats-local.yaml","shadows":[]}]}
|
|
1816
1902
|
```
|
|
1817
1903
|
|
|
1818
1904
|
- **list**: `selected` is `null`, `{home, instance}` or `{soul, agentsRoot}`;
|
|
1819
1905
|
`level` and `file` are `null` without an `oats-local.yaml` (the set is then
|
|
1820
1906
|
empty). An environment literal is `{redacted: true}`, a reference
|
|
1821
|
-
`{fromEnv}`; values never leave the file.
|
|
1907
|
+
`{fromEnv}`; values never leave the file. `default` (0.32, feature
|
|
1908
|
+
`launch-config-default`) is always a boolean: `true` marks this host's
|
|
1909
|
+
default for the configuration's harness.
|
|
1822
1910
|
- **set**/**remove**: `{name, action, level, file, before, after,
|
|
1823
1911
|
effective}`. `set --file` takes `{harness, executable?, args?, env?, model?,
|
|
1824
|
-
yolo?}` (`-` reads stdin)
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
`
|
|
1912
|
+
yolo?, default?}` (`-` reads stdin); it replaces the whole entry and refuses
|
|
1913
|
+
a key it does not know, so an editor sends `default` back to keep it.
|
|
1914
|
+
`default: false` is written as its absence. A second `default: true` for a
|
|
1915
|
+
harness is `E_LAUNCH_CONFIG_INVALID` with `details: {harness,
|
|
1916
|
+
configurations: [<the declared one>, <this one>]}` and nothing is written;
|
|
1917
|
+
moving the default is two writes, never an automatic move. `--keep-env`
|
|
1918
|
+
keeps the declared environment when `env` is omitted. Routed with
|
|
1919
|
+
`--server`, a definition with `default: true` to a host that does not
|
|
1920
|
+
advertise `launch-config-default` is `E_REMOTE_INCOMPATIBLE` before
|
|
1921
|
+
anything is sent (`default: false` is dropped). Errors: `E_LOCAL_MISSING`,
|
|
1922
|
+
`E_BAD_ARGS` (including `--home`/`--soul`), `E_LAUNCH_CONFIG_UNKNOWN`,
|
|
1923
|
+
`E_LAUNCH_CONFIG_INVALID`, `E_CONFIG_BROKEN`, `E_HOME_UNKNOWN`,
|
|
1924
|
+
`E_REMOTE_INCOMPATIBLE`.
|
|
1828
1925
|
- **preview** (read-only) answers `{context, selected, selection: {source,
|
|
1829
1926
|
launchConfig, harness, model, yolo}, harness, model, modelSource, yolo,
|
|
1830
|
-
launchConfig, launchConfigSource, executable: {path,
|
|
1831
|
-
resolvedFrom}, argv, environment: [{name, fromEnv} | {name, redacted:
|
|
1927
|
+
launchConfig, launchConfigSource, launchConfigDefault, executable: {path,
|
|
1928
|
+
declared, resolvedFrom}, argv, environment: [{name, fromEnv} | {name, redacted:
|
|
1832
1929
|
true} | {name, reference: true}], command (redacted), prompt, hooks,
|
|
1833
1930
|
preflight: [{check, ok, detail}], ok}`.
|
|
1834
1931
|
|
|
1932
|
+
`launchConfigDefault` (0.32) is `true` when `launchConfig` is this host's
|
|
1933
|
+
default for the harness rather than a chosen configuration; the spawn preview
|
|
1934
|
+
carries the same top-level field, `instance.json` records it as
|
|
1935
|
+
`launch.launchConfigDefault: true`, and `inspect --json` of a home answers
|
|
1936
|
+
`instance.launchConfig` and `instance.launchConfigDefault`. The closed `Launch`
|
|
1937
|
+
object is unchanged: its `effective.launchConfig` names the default
|
|
1938
|
+
configuration.
|
|
1939
|
+
|
|
1835
1940
|
A successful envelope can carry `ok: false`: show the failed `preflight`
|
|
1836
1941
|
checks. The prompt is named, never the task body. A home predating launch
|
|
1837
1942
|
recipes answers its frozen command with `selection.source: "frozen-command"`
|
package/docs/desktop.md
CHANGED
|
@@ -110,6 +110,22 @@ registered remote workspace the timer and definitions live on that server, so
|
|
|
110
110
|
they do not depend on the Mac staying awake. See [Schedules](schedules.md) for
|
|
111
111
|
the CLI, cron semantics, observed outcomes and recovery commands.
|
|
112
112
|
|
|
113
|
+
## Instances on servers
|
|
114
|
+
|
|
115
|
+
Every instance a registered server reports shows in its workspace's roster, whoever spawned it. You
|
|
116
|
+
can open its terminal, start, restart, stop and remove it, and read its readiness, activity, Git
|
|
117
|
+
and diffs, as for a local one. Only its pull request is not read here, since the forge reads this
|
|
118
|
+
computer's clones. Every command goes to the server by the instance's home (`--server <id> --home
|
|
119
|
+
<path>`), never by a bare name. Stop and Remove show the plan the server makes, and confirm
|
|
120
|
+
against it.
|
|
121
|
+
|
|
122
|
+
A read waits for the server: the view says "Reading from <server>…", and gives up after about
|
|
123
|
+
45 seconds with "Couldn't reach <server>." When the server refuses, you see its code and message;
|
|
124
|
+
nothing is read from this computer in its place. A row that can't be opened says why on the row:
|
|
125
|
+
Herdr no longer supported, gone from <server>, not reachable on <server>, or <server> not reached.
|
|
126
|
+
For an instance a server no longer lists, the reason names the command that removes it from this
|
|
127
|
+
computer (`oats server forget <server> --instance <name>`).
|
|
128
|
+
|
|
113
129
|
## Remote terminals
|
|
114
130
|
|
|
115
131
|
A terminal for an instance on a registered server is a viewer over ssh
|
package/docs/implementation.md
CHANGED
|
@@ -44,6 +44,7 @@ published to npm. Its developer docs are in
|
|
|
44
44
|
| module | owns |
|
|
45
45
|
|---|---|
|
|
46
46
|
| `remote.mjs` | repo refs, reading Git remotes, content digests |
|
|
47
|
+
| `local-inputs.mjs` | the local configuration a command read, for `observation.localRevision` |
|
|
47
48
|
| `workspace.mjs` | workspace, membership and soul files; discovery |
|
|
48
49
|
| `resolve.mjs` | a soul's resolution: capabilities, slots, provenance |
|
|
49
50
|
| `packages.mjs` | `packages:`, the catalog, `oats sync`, `oats-lock.json` |
|
|
@@ -62,6 +63,63 @@ The kernel is runtime-neutral: nothing in `lib/` depends on a harness or on
|
|
|
62
63
|
a provider. Provider behaviour lives in capabilities; the kernel supplies
|
|
63
64
|
their contracts ([layers](layers.md)).
|
|
64
65
|
|
|
66
|
+
### The remote read path
|
|
67
|
+
|
|
68
|
+
Every CLI command owns one read session (`createReadSession` in
|
|
69
|
+
`remote.mjs`, carried to every remote call as `remoteOptions.session`; the
|
|
70
|
+
CLI closes it when the command ends). A library caller without a session
|
|
71
|
+
gets the plain per-call behaviour. Within a session:
|
|
72
|
+
|
|
73
|
+
- a head is observed once per (cache repo, ref), and a commit peeled once; at
|
|
74
|
+
most eight observations run at once (`OBSERVE_LIMIT`), each holding its slot
|
|
75
|
+
for all its git work (the `ls-remote` and the fetch of the commit it names,
|
|
76
|
+
or the fetch of a reused record's commit);
|
|
77
|
+
- a whole-workspace discovery prefetches its members' heads together with the
|
|
78
|
+
host's (`prefetchMembers` in `workspace.mjs`): the member list comes from the
|
|
79
|
+
host's last observation record and the parsed `workspace` entry at that
|
|
80
|
+
commit, never from a git process, and the answer still uses the list at the
|
|
81
|
+
host commit observed now. A prefetched failure is adopted by the member's own
|
|
82
|
+
observation, not retried in the command. A prefetch no caller adopts (the
|
|
83
|
+
host could not be observed, or the member was dropped since) is abandoned:
|
|
84
|
+
one still queued runs no git. `observeWorkspace` alone (the `teams` reads,
|
|
85
|
+
`inspect --home`) never prefetches;
|
|
86
|
+
- closing the session (the end of the command, or `process.exit`) rejects
|
|
87
|
+
every queued observation and aborts every git child still running for it
|
|
88
|
+
(the session's `AbortSignal` rides every `runGit`). `runGit` starts git as
|
|
89
|
+
its own process group, so a timeout, an output overflow or an abort kills
|
|
90
|
+
git's ssh or remote helper with it; before a capability
|
|
91
|
+
command runs its provider, the CLI ends the idle batch readers
|
|
92
|
+
(`closeBatches`);
|
|
93
|
+
- a commit's tree is listed once (`git ls-tree -r -t -l`, bounded by
|
|
94
|
+
`TREE_INDEX_BUDGET`; anything odd falls back to the per-path reads), and
|
|
95
|
+
blobs come from one `git cat-file --batch` reader per cache repo (at most
|
|
96
|
+
12 open, killed through `process-group.mjs` on timeout and at close);
|
|
97
|
+
- discovery reads members eight at a time (`DISCOVERY_CONCURRENCY`) with
|
|
98
|
+
serial results: declaration order, the first failure in that order. The
|
|
99
|
+
observations and the member reads are two pools, so a discovery runs at
|
|
100
|
+
most sixteen short-lived git processes at once (eight of them fetches at
|
|
101
|
+
most), plus up to twelve cat-file readers: twenty-eight git processes. One
|
|
102
|
+
shared pool would deadlock: a member read holding a slot waits on its
|
|
103
|
+
member's observation, which needs a slot of its own.
|
|
104
|
+
|
|
105
|
+
Across commands, `memoAtCommit` keeps parsed reads under
|
|
106
|
+
`<cache>/.parsed/<kernel fingerprint>/`, keyed by (repo key, full commit,
|
|
107
|
+
item). The items: `workspace` (the workspace file), `membership` (a member's
|
|
108
|
+
backlink outcome), `enumerate` (a member's souls and capabilities),
|
|
109
|
+
`package-soul` and `external-soul` (one soul file each; their error handling
|
|
110
|
+
differs), `package-manifests` (a package's manifests), `list` (a skill
|
|
111
|
+
listing) and `tree-oids` (the tree ids of a set of directories). An entry is only ever a pure function of those bytes and this kernel's
|
|
112
|
+
code, never local state and never a transient error; it is written
|
|
113
|
+
atomically, a corrupt one is a miss, and `pruneStores` bounds the store
|
|
114
|
+
(`PARSED_LIMITS`, least recently used first). `--max-age` adds the observation
|
|
115
|
+
store `<cache>/.observed/` ([Observation reuse](desktop-cli-api.md#observation-reuse-feature-observe-max-age-oats-0311)):
|
|
116
|
+
one record per (repo key, ref args, url digest), so two spellings of one repo
|
|
117
|
+
keep a record each; the url itself is never written. Adding a cached item
|
|
118
|
+
means choosing an item name unique to its producer (the item string its
|
|
119
|
+
call site passes to `memoAtCommit`, or to `atCommit` in `workspace.mjs`) and
|
|
120
|
+
adding it to `test/parsed-cache.test.mjs`; `test/read-path-scale.test.mjs` pins the member
|
|
121
|
+
scaling by call count.
|
|
122
|
+
|
|
65
123
|
## Tests and gates
|
|
66
124
|
|
|
67
125
|
| command | what it checks |
|
|
@@ -60,7 +60,8 @@
|
|
|
60
60
|
"description": "Environment for the harness. A string is a literal (non-secret by contract; still redacted in every answer). {fromEnv: NAME} is resolved from the execution host's environment at start time; the reference, never the value, is recorded. A missing reference refuses the start before anything stops."
|
|
61
61
|
},
|
|
62
62
|
"model": { "type": "string", "minLength": 1, "description": "Model for this configuration's harness; overrides the soul default when this configuration is selected." },
|
|
63
|
-
"yolo": { "type": "boolean", "description": "Permission bypass for this configuration; overrides scope and soul defaults when selected." }
|
|
63
|
+
"yolo": { "type": "boolean", "description": "Permission bypass for this configuration; overrides scope and soul defaults when selected." },
|
|
64
|
+
"default": { "type": "boolean", "description": "This host's baseline for its harness (0.32, feature launch-config-default): a new launch that picks this harness without naming a configuration (a soul's or souls.launch preference, --harness, the host default) runs this configuration's executable, args, env and yolo; its model is the last fallback. At most one per harness. `--launch-config none` bypasses it. OATS 0.31 and older refuse the key." }
|
|
64
65
|
}
|
|
65
66
|
}
|
|
66
67
|
},
|
|
@@ -69,8 +69,11 @@ Found by the rehearsals, and fixed before the tag:
|
|
|
69
69
|
## After the tag
|
|
70
70
|
- **Flag day:** the oats.engineering release with the souls' `launch:` (experts on Claude Code +
|
|
71
71
|
Opus 5.5; `code-reviewer` on Codex + Astra; compat `>=0.30.0`), and committing the shared team id.
|
|
72
|
-
- **0.30.1:**
|
|
73
|
-
-
|
|
72
|
+
- **0.30.1:** one release, cut when the Desktop load work has merged (the kernel read-path PR with
|
|
73
|
+
`observe-max-age` and `observation.localRevision`, Desktop #321 and #322), or on 2026-09-30 18:00 UTC,
|
|
74
|
+
whichever comes first; what has not merged by then goes to 0.30.2. Retire of homes whose session is
|
|
75
|
+
gone (#299) and automations.trust (#300) shipped in 0.30.0. The kernel items below have no owner yet:
|
|
76
|
+
each rides 0.30.1 only if it merges before the cut.
|
|
74
77
|
- deployment-scope capability commands without `--soul`;
|
|
75
78
|
- the case-8 pre-check (`E_TEAM_UNCONFIGURED`).
|
|
76
79
|
- the kernel refuses an unrecognised spawn argument (a bare `join=oats` without
|
|
@@ -79,5 +82,5 @@ Found by the rehearsals, and fixed before the tag:
|
|
|
79
82
|
FIRST on the instance's PATH and records it in the launch recipe, so plain `oats` inside an
|
|
80
83
|
instance is the kernel that made it (two kernels side by side: a 0.30 home ran the global 0.24.6
|
|
81
84
|
and `oats aweb` was `E_UNKNOWN_COMMAND`; the injected instructions tell agents to run plain `oats`).
|
|
82
|
-
- **
|
|
83
|
-
|
|
85
|
+
- **Done:** item L merged in #314 (d4f68da9): the remaining legacy `agents/` soul trees and the
|
|
86
|
+
`validate:okf` gate are removed.
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# OATS 0.32.0
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
|
|
5
|
+
- **A default launch configuration per harness.** `default: true` on a
|
|
6
|
+
launch configuration in `oats-local.yaml` makes it this machine's baseline
|
|
7
|
+
for its harness. Every new launch of that harness that names no
|
|
8
|
+
configuration runs its executable, args, env and `yolo`: a soul's own
|
|
9
|
+
`launch:`, an inline `souls.launch` preference, `--harness`, the host
|
|
10
|
+
default. For example, every claude instance on a machine runs with its
|
|
11
|
+
`CLAUDE_CONFIG_DIR`:
|
|
12
|
+
|
|
13
|
+
```yaml
|
|
14
|
+
launch-configs:
|
|
15
|
+
mine:
|
|
16
|
+
harness: claude
|
|
17
|
+
default: true
|
|
18
|
+
env: { CLAUDE_CONFIG_DIR: /home/me/.claude-personal }
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
The model still comes from whatever chose the harness; the default's
|
|
22
|
+
`model` is only the last fallback. A named configuration runs as declared,
|
|
23
|
+
and `--launch-config none` asks for the bare harness. One default per
|
|
24
|
+
harness (a second is refused, naming both). `oats launch-config list`,
|
|
25
|
+
`spawn --preview`, `launch-config preview`, `instance.json` and
|
|
26
|
+
`oats inspect --home` say when a launch came from the default
|
|
27
|
+
(`launchConfigDefault`), yolo included. Existing instances keep their
|
|
28
|
+
launch until `--reselect-launch` or a respawn, and meanwhile show the
|
|
29
|
+
`launch-changed` readiness warning. Feature
|
|
30
|
+
`launch-config-default`
|
|
31
|
+
([configuration.md](../configuration.md#the-harness-default)).
|
|
32
|
+
**Declaring a default needs OATS 0.32+ for every kernel that reads this
|
|
33
|
+
deployment:** OATS 0.31 and older refuse the whole `oats-local.yaml`
|
|
34
|
+
(`E_WORKSPACE_SCHEMA`). A routed `launch-config set --server` with
|
|
35
|
+
`default: true` to an older host is refused (`E_REMOTE_INCOMPATIBLE`)
|
|
36
|
+
before anything is sent.
|
|
37
|
+
|
|
38
|
+
- **Every instance a server reports is first-class in the Desktop, whoever
|
|
39
|
+
spawned it.** A remote row opens its terminal, starts, restarts, stops and
|
|
40
|
+
retires, and shows its readiness, activity, Git and diffs, just like a local
|
|
41
|
+
one; only its pull request is not read here, since the forge reads this
|
|
42
|
+
computer's clones. Stop and Remove keep their confirmations: the plan and
|
|
43
|
+
its guarded apply run on the instance's machine. Every command goes to the
|
|
44
|
+
server by the instance's home (`--server <id> --home <path>`), never by a
|
|
45
|
+
bare name, so two instances with the same name stay apart. While a read is
|
|
46
|
+
in flight, the view says "Reading from <server>…". When the server refuses,
|
|
47
|
+
it shows the server's code and message under a headline that usually names
|
|
48
|
+
the server; it never falls back to a local read. A remote stop or remove
|
|
49
|
+
that loses its link reads as an unknown outcome, never a failure, and the
|
|
50
|
+
server's roster is read again at once
|
|
51
|
+
([desktop.md](../desktop.md#instances-on-servers)).
|
|
52
|
+
- **A row that can't be opened says why, on the row.** The roster shows a short
|
|
53
|
+
reason where it said "state unknown": Herdr no longer supported, gone from
|
|
54
|
+
<server>, not reachable on <server>, or <server> not reached. The full
|
|
55
|
+
sentence stays in the row's tooltip and its actions menu. For an instance a
|
|
56
|
+
server no longer lists, it names the command that removes it from this
|
|
57
|
+
computer (`oats server forget <server> --instance <name>`).
|
|
58
|
+
- **This computer's OATS 0.31 decides which server rows can be used.** The
|
|
59
|
+
Desktop accepts OATS CLIs up to 0.32.x. With a local OATS before 0.31, which
|
|
60
|
+
does not report that fact, an instance spawned from this computer stays
|
|
61
|
+
usable through its saved route, as before; its routed reads and plans ask you
|
|
62
|
+
to update OATS here.
|
|
63
|
+
- **A team card lists its members wherever they run.** On a local workspace's
|
|
64
|
+
Teams page, each team with a provider id lists every instance in it: this
|
|
65
|
+
workspace's, and every registered server's whose messaging identity is in
|
|
66
|
+
that team. They are grouped by machine ("This computer" first), with each
|
|
67
|
+
one's state in words. A member's name shows its roster row, where every
|
|
68
|
+
action lives; **Terminal** opens a running member there. A server whose
|
|
69
|
+
last roster read failed keeps its last-known members under "<server> · not
|
|
70
|
+
reached"; servers with nothing to show are named under the page head. The
|
|
71
|
+
card's count reads "N members · M on other machines". The Desktop reads
|
|
72
|
+
this from what it already holds and runs nothing new
|
|
73
|
+
([desktop-teams.md](../../packages/desktop/docs/desktop-teams.md#team-members)).
|
|
74
|
+
- **The spawn dialog asks "Where to run" up front** (it was "Run on", under
|
|
75
|
+
Developer settings). "This computer" comes first, then each registered
|
|
76
|
+
server. A server is disabled, with the reason, when it wasn't reached or
|
|
77
|
+
the roster knows only an older registration of it. With a server chosen,
|
|
78
|
+
the dialog says where the instance will run, and the relationship picker
|
|
79
|
+
lists only that server's instances: a relation never crosses machines. When
|
|
80
|
+
the server refuses the spawn (a soul it doesn't offer, for example), the
|
|
81
|
+
dialog says so in the server's own words.
|
|
82
|
+
- **The Desktop keeps and sets a harness's default launch configuration.**
|
|
83
|
+
Editing a configuration marked `default: true` saves it with its default
|
|
84
|
+
intact, and with an OATS that has `launch-config-default` the editor offers
|
|
85
|
+
"Default for <harness> on this machine" for this computer's own
|
|
86
|
+
configurations. A remote host's default survives editing too, but is set on
|
|
87
|
+
that host. A second default for the same harness is refused in OATS's own
|
|
88
|
+
words; unset the old one first.
|
|
89
|
+
|
|
90
|
+
- **Faster read verbs, and `--max-age <s>` to reuse recent remote heads**
|
|
91
|
+
(feature `observe-max-age`). `status`, `workspace status`, `souls`,
|
|
92
|
+
`capabilities` and `inspect` start far fewer git processes, cache what
|
|
93
|
+
they parse by commit, and start the members' remote reads together with the
|
|
94
|
+
host's. With `--max-age` they reuse a head observed in the last `<s>`
|
|
95
|
+
seconds and report `observation {observedAt, reused, localRevision}`
|
|
96
|
+
(`localRevision` changes whenever the local configuration read for the
|
|
97
|
+
answer does). See
|
|
98
|
+
[Observation reuse](../desktop-cli-api.md#observation-reuse-feature-observe-max-age-oats-0311).
|
|
99
|
+
|
|
100
|
+
## Fixed
|
|
101
|
+
|
|
102
|
+
- **A remote spawn with an empty opening instruction starts it waiting**, as
|
|
103
|
+
the dialog says. It was refused ("--task needs a value").
|
|
104
|
+
- **Focus stays put on the Workspace's Teams, Capabilities and Setup pages.**
|
|
105
|
+
Every roster poll rebuilt the page, since the workspace status it compared
|
|
106
|
+
carries a fresh observation time each time, so keyboard focus (and the
|
|
107
|
+
caret in the add-team form) fell back to the page every few seconds.
|
|
108
|
+
- **A click on a Desktop instance's actions menu does what it says.** With the
|
|
109
|
+
pointer over the open menu, the row no longer counted as hovered, so its
|
|
110
|
+
tools, the menu included, went hidden and a click on an item did nothing;
|
|
111
|
+
the keyboard still worked.
|
|
112
|
+
- **The Stop and Remove confirmations show only their own choices.** Stop
|
|
113
|
+
showed Remove's "delete the worktree" and "delete the local branch" options
|
|
114
|
+
(enabled, and ignored), and Remove showed Stop's "include recorded children".
|
|
115
|
+
|
|
116
|
+
- **A remote read that times out no longer leaves its ssh or https helper
|
|
117
|
+
running.** The kernel killed git but not the ssh or `git-remote-https` it
|
|
118
|
+
had started, which stayed connected to an unreachable host until its own
|
|
119
|
+
connection gave up. Git now runs as its own process group, and a timeout
|
|
120
|
+
(or the end of the command) kills the whole group.
|
|
121
|
+
|
|
122
|
+
- **A capability command whose provider dies of a signal exits as the shell
|
|
123
|
+
reports it** (128 + the signal number, 130 for a Ctrl-C), not 1.
|
|
124
|
+
|
|
125
|
+
- **`oats --help` piped into another program is no longer cut off at 8 KB.**
|
|
126
|
+
The top-level usage (about 20 KB) now arrives whole, including the
|
|
127
|
+
"Observation reuse" section; exit codes are unchanged.
|
|
128
|
+
|
|
129
|
+
## Removed
|
|
130
|
+
|
|
131
|
+
- **`oats-claude-config` is no longer read.** The one-line file that named
|
|
132
|
+
the claude binary for every deployment below it is replaced by the claude
|
|
133
|
+
default launch configuration. A new claude launch with one in reach is
|
|
134
|
+
refused with `E_CLAUDE_CONFIG_REMOVED`, naming the file and the
|
|
135
|
+
configuration to declare instead (`harness: claude`, `executable: <the
|
|
136
|
+
name it held>`, `default: true`); then delete the file. Instances already
|
|
137
|
+
launched with it keep their recorded executable.
|
package/lib/automations.mjs
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
import { spawnSync } from "node:child_process";
|
|
35
35
|
import { mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
36
36
|
import { dirname, join } from "node:path";
|
|
37
|
+
import { recordLocalInput } from "./local-inputs.mjs";
|
|
37
38
|
import YAML from "yaml";
|
|
38
39
|
import { parseConfigData } from "./config-data.mjs";
|
|
39
40
|
|
|
@@ -192,8 +193,11 @@ export function soulOriginOf(index, soul) {
|
|
|
192
193
|
|
|
193
194
|
export const snapshotPath = (dep) => join(dep, ".agents", "automations", "snapshot.json");
|
|
194
195
|
export function readSnapshot(dep) {
|
|
196
|
+
let text;
|
|
197
|
+
try { text = readFileSync(snapshotPath(dep), "utf8"); } catch { recordLocalInput(snapshotPath(dep), null); return null; }
|
|
198
|
+
recordLocalInput(snapshotPath(dep), text);
|
|
195
199
|
try {
|
|
196
|
-
const doc = JSON.parse(
|
|
200
|
+
const doc = JSON.parse(text);
|
|
197
201
|
return isObject(doc) && KIND_NAMES.every((k) => Array.isArray(doc[`${k}s`])) ? doc : null;
|
|
198
202
|
} catch { return null; }
|
|
199
203
|
}
|