@awebai/oats 0.30.3 → 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 +216 -79
- package/docs/configuration.md +68 -8
- package/docs/desktop-cli-api.md +233 -27
- package/docs/desktop-instance-start.md +3 -1
- package/docs/desktop.md +36 -0
- package/docs/execution-targets.md +53 -36
- package/docs/implementation.md +59 -1
- package/docs/integrations.md +1 -1
- package/docs/oats-local.schema.json +10 -1
- package/docs/official-catalog.md +1 -1
- package/docs/packages.md +2 -2
- package/docs/plans/0.30-close-out.md +7 -4
- package/docs/release-notes/v0.31.0.md +130 -0
- package/docs/release-notes/v0.32.0.md +137 -0
- package/docs/schedules.md +4 -1
- package/docs/servers.md +94 -25
- package/docs/souls-and-instances.md +2 -2
- package/lib/attachments.mjs +2 -1
- package/lib/automations.mjs +5 -1
- package/lib/core.mjs +241 -238
- package/lib/errors.mjs +17 -0
- package/lib/instance-inspect.mjs +13 -4
- 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/schedule.mjs +21 -8
- package/lib/servers.mjs +272 -45
- package/lib/session-input.mjs +9 -23
- package/lib/session-viewer.mjs +0 -5
- package/lib/triggers.mjs +10 -8
- package/lib/workspace.mjs +163 -56
- package/package-catalog.json +1 -1
- package/package.json +1 -1
- package/lib/herdr.mjs +0 -143
package/docs/servers.md
CHANGED
|
@@ -26,13 +26,59 @@ oats server remove build
|
|
|
26
26
|
- `--path` prepends directories to the minimal remote PATH of every routed
|
|
27
27
|
command (`~/.local/bin:/opt/pi/bin`), where the remote spawn looks for the
|
|
28
28
|
harness binary.
|
|
29
|
-
- `--herdr`
|
|
30
|
-
saved route
|
|
31
|
-
|
|
29
|
+
- `--herdr` is refused (`E_HERDR_REMOVED`): Herdr was removed in 0.31.0. A
|
|
30
|
+
registration or saved route that still records `herdrPath` loads; the field
|
|
31
|
+
is ignored, never written or printed.
|
|
32
32
|
- `--label` sets a display name. `--replace` overwrites an existing id.
|
|
33
33
|
- Registrations live in `~/.oats/servers.json` on this machine, never in a
|
|
34
34
|
repository.
|
|
35
35
|
|
|
36
|
+
## Connections
|
|
37
|
+
|
|
38
|
+
Every routed ssh call carries these options, before `--` and the host:
|
|
39
|
+
|
|
40
|
+
| Option | Why |
|
|
41
|
+
|---|---|
|
|
42
|
+
| `BatchMode=yes`, `ConnectTimeout=15` | a prompt or an unreachable host fails fast |
|
|
43
|
+
| `ServerAliveInterval=15`, `ServerAliveCountMax=3` | a link that died without a reset ends the call, an attached viewer included, within about 60 s |
|
|
44
|
+
| `ControlMaster=auto`, `ControlPath=~/.oats/ssh/%C`, `ControlPersist=60` | every call to one host (the probe, the command, a viewer, concurrent Desktop reads) shares one authenticated connection, kept 60 s after its last client |
|
|
45
|
+
|
|
46
|
+
- Options given with `-o` override `~/.ssh/config`, so these replace any
|
|
47
|
+
`ControlMaster`, `ControlPath` or `ControlPersist` set there for the host.
|
|
48
|
+
Everything else in your ssh config (users, keys, `ProxyJump`, host
|
|
49
|
+
verification) applies as usual; a `ProxyJump` host is reached once per
|
|
50
|
+
master.
|
|
51
|
+
- `~/.oats/ssh` (under `OATS_HOME_DIR` when set) is created mode 0700. OATS
|
|
52
|
+
does not use it if it is owned by another user or writable by group or
|
|
53
|
+
others.
|
|
54
|
+
- ssh binds the control socket at the path plus 17 bytes, within the 104-byte
|
|
55
|
+
socket path limit of macOS, so the directory path may be at most 45 bytes.
|
|
56
|
+
`/Users/<name>/.oats/ssh` fits for any name up to 23 characters. The path
|
|
57
|
+
must also be one ssh reads literally: letters, digits and `. _ / + , : @ =
|
|
58
|
+
-` only (a space, `%`, `$`, a quote or `#` is ssh syntax).
|
|
59
|
+
- When the path does not fit, has other characters, or the directory is not
|
|
60
|
+
private, calls run with `ControlPath=none` instead: no connection sharing,
|
|
61
|
+
including any your ssh config sets up; keepalives still apply. The command
|
|
62
|
+
warns once per server on stderr, naming the path and the fix (an
|
|
63
|
+
`OATS_HOME_DIR` or `HOME` that is shorter or plain).
|
|
64
|
+
- A master left stale by a network drop is replaced by ssh on the next call.
|
|
65
|
+
- The version probe (`oats version --json`) is asked once per process per
|
|
66
|
+
server and target. `server add --replace` to another target forgets it.
|
|
67
|
+
- When ssh fails under an attached viewer (a lost link, or one never made),
|
|
68
|
+
`session attach --server` exits 255 and says that, if the link was lost,
|
|
69
|
+
the instance keeps running on the server, with the command to reattach.
|
|
70
|
+
When ssh fails before the viewer opens (the version probe, or a name
|
|
71
|
+
resolved through the host's roster), it also exits 255, with `oats: ssh to
|
|
72
|
+
<host> failed: …` (`--json`: the `E_SSH` envelope). Every other refusal
|
|
73
|
+
(an incompatible host, an unknown or ambiguous name, bad arguments) exits 1,
|
|
74
|
+
and so does an ssh that cannot be run on this machine at all: no link can
|
|
75
|
+
come back.
|
|
76
|
+
- Every routed command reports ssh's own failure as `E_SSH` (`ssh to <host>
|
|
77
|
+
failed: …`). When ssh never started on this machine, the `--json` envelope
|
|
78
|
+
says so with `error.details: {"sshStarted": false}`; without details, ssh
|
|
79
|
+
ran and the link failed
|
|
80
|
+
([desktop-cli-api.md](desktop-cli-api.md#ssh-failures-e_ssh)).
|
|
81
|
+
|
|
36
82
|
## Run there
|
|
37
83
|
|
|
38
84
|
```bash
|
|
@@ -50,14 +96,18 @@ sent as text or on stdin, never as paths.
|
|
|
50
96
|
|---|---|---|
|
|
51
97
|
| `spawn` | registered workspace | the requested harness, backend and yolo option; `launch-config` for `--launch-config`; `schedule` for a wake schedule |
|
|
52
98
|
| `status` | registered workspace | |
|
|
53
|
-
| `retire` |
|
|
54
|
-
| `session inspect`, `session attach` |
|
|
55
|
-
| `session start`, `session restart` |
|
|
56
|
-
| `session upload` |
|
|
99
|
+
| `retire` | the instance's home | `retire-home` to retire by exact home |
|
|
100
|
+
| `session inspect`, `session attach` | the instance's home | `session` |
|
|
101
|
+
| `session start`, `session restart` | the instance's home | `session-start`; `session-restart`; `launch-config` when `--launch-config`, `--harness` or `--yolo` is given |
|
|
102
|
+
| `session upload` | the instance's home | `session-upload` |
|
|
57
103
|
| `okf harvest --instance <name>` | the saved home | `harvest` |
|
|
58
104
|
| `schedule ...` | registered workspace | `schedule` |
|
|
59
|
-
| `launch-config list\|set\|remove\|preview` | `--dir`,
|
|
60
|
-
| `inspect`, `operation run` | `--dir`,
|
|
105
|
+
| `launch-config list\|set\|remove\|preview` | `--dir`, the instance's home, or the registered workspace | `launch-config` |
|
|
106
|
+
| `inspect`, `operation run` | `--dir`, the instance's home, or the registered workspace | `operations` |
|
|
107
|
+
| `readiness` | the instance's home, `--dir`, or the registered workspace | `readiness` (`readinessApi: 2`) |
|
|
108
|
+
| `instance events` | the instance's home | `instance-events-2` (`eventsApi: 2`) |
|
|
109
|
+
| `instance git`, `instance diff` | the instance's home; the Git runs there | `instance-git` (`instanceGitApi: 1`) |
|
|
110
|
+
| `instance stop --plan\|--apply`, `retire --plan`, `retire --plan-revision … --idempotency-key …` | the instance's home | `lifecycle-plans` (`lifecycleApi: 1`) |
|
|
61
111
|
|
|
62
112
|
Before every routed command except `status`, the kernel reads the server's
|
|
63
113
|
`oats version --json`. A remote OATS older than 0.22.1 is refused, and so is a
|
|
@@ -66,19 +116,36 @@ checked against the harness it would use (the flag, or the soul's default as
|
|
|
66
116
|
the remote roster reports it). A host that advertises no harness list is
|
|
67
117
|
assumed to run only pi and claude, on tmux, with no launch options.
|
|
68
118
|
|
|
69
|
-
**Addressing an instance.** Instance commands
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
119
|
+
**Addressing an instance.** Instance commands (`retire`, the `session`
|
|
120
|
+
commands, `inspect`, `operation`, `launch-config`) reach any instance on the
|
|
121
|
+
server, whoever spawned it, by `--home </remote/home>` or by name
|
|
122
|
+
(`--instance <name>`, or the positional name of `retire`):
|
|
123
|
+
|
|
124
|
+
- A name spawned from this machine resolves through its saved route.
|
|
125
|
+
- Any other name resolves through the server's roster (one `status --json`
|
|
126
|
+
in the registered workspace) to its one home. A name that two homes carry
|
|
127
|
+
there is `E_AMBIGUOUS`, listing the homes (`error.details.candidates`);
|
|
128
|
+
pass `--home`. A name the roster does not list is `E_SNAPSHOT_UNKNOWN`.
|
|
129
|
+
- A `--home` no saved route owns is sent through the registration's target;
|
|
130
|
+
the server's kernel decides whether it is an instance home and its refusal
|
|
131
|
+
is relayed as is.
|
|
132
|
+
- The home is the identity; use it when two souls on the host own
|
|
133
|
+
same-named instances. A name and a home that disagree are refused
|
|
134
|
+
(`E_HOME_MISMATCH`).
|
|
135
|
+
|
|
136
|
+
**`--dir` with `--server`.** For `inspect`, `operation`, `launch-config`,
|
|
137
|
+
`readiness`, `instance`, and a `retire --plan` or guarded retire apply, an
|
|
138
|
+
explicit `--dir` names a directory on the server and travels as is. Every
|
|
139
|
+
other routed command refuses `--dir`; its scope comes from the
|
|
140
|
+
registration.
|
|
77
141
|
|
|
78
142
|
**Not routed.** `session input` runs on the execution host, where schedules
|
|
79
143
|
and messaging capabilities call it. `session restart --stop-grace` is refused
|
|
80
144
|
with `--server`; the remote default applies. `session attach --print` shows
|
|
81
|
-
the ssh command without running it.
|
|
145
|
+
the ssh command without running it. A server without the `session` commands
|
|
146
|
+
(before 0.22.2) is refused with the tmux command to attach there directly,
|
|
147
|
+
naming the session and window its roster records for the instance (else
|
|
148
|
+
`pi-agents`, that kernel's default).
|
|
82
149
|
|
|
83
150
|
## The roster and harvest
|
|
84
151
|
|
|
@@ -91,8 +158,13 @@ The **roster** is what the Desktop shows: one group per server id and route
|
|
|
91
158
|
target (host and workspace), with the registration (present or not), the
|
|
92
159
|
probe result, the remote souls, the instances joined with saved routes
|
|
93
160
|
(`savedRoute`, `running` or `null` when unknown, `retirePending`,
|
|
94
|
-
`rollbackIncomplete`, `missingRemotely`), and `retireFailures`
|
|
95
|
-
self-retirements that failed there).
|
|
161
|
+
`rollbackIncomplete`, `missingRemotely`, `addressable`), and `retireFailures`
|
|
162
|
+
(deferred self-retirements that failed there). Each instance row also relays
|
|
163
|
+
the host's own facts from its `status --json`: `identity`,
|
|
164
|
+
`identityAddress`, `teams`, `startedAt`, `createdAt`, `model`,
|
|
165
|
+
`runtimeState`, `parentInstance`, `siblingInstance`, `relation`,
|
|
166
|
+
`relativeTo` and `spawnOrigin`. A fact the host does not supply is `null`
|
|
167
|
+
(an older host, or a saved route the host no longer lists). A removed or edited registration keeps
|
|
96
168
|
its group from the saved routes. State is pulled on every call within
|
|
97
169
|
`--per-target` (default 20 s) of a total `--budget` (default 45 s); a group
|
|
98
170
|
not reached is reported with `E_ROSTER_BUDGET`. `--server <id>` narrows it.
|
|
@@ -103,8 +175,8 @@ instance's saved home on the server and relays its envelope.
|
|
|
103
175
|
## What this machine keeps
|
|
104
176
|
|
|
105
177
|
A **saved route** per remote instance, under `~/.oats/remote/<server>/`,
|
|
106
|
-
written at spawn: the ssh host, workspace, oats path
|
|
107
|
-
|
|
178
|
+
written at spawn: the ssh host, workspace, oats path and PATH prefix, and the
|
|
179
|
+
remote home. Retire, harvest and session commands use
|
|
108
180
|
the saved route, not the current registration, so editing or removing a
|
|
109
181
|
registration never orphans a remote home. The route is removed when the
|
|
110
182
|
remote kernel reports the home gone.
|
|
@@ -123,8 +195,5 @@ remote kernel reports the home gone.
|
|
|
123
195
|
|
|
124
196
|
## Limits
|
|
125
197
|
|
|
126
|
-
- Lifecycle actions need a saved route from this machine. An instance the
|
|
127
|
-
remote reports but that was spawned elsewhere appears in the roster with
|
|
128
|
-
`savedRoute: false` and is read-only here.
|
|
129
198
|
- No Git runs over SSH: repository operations always run on the server, by
|
|
130
199
|
its kernel, in its deployment.
|
|
@@ -85,8 +85,8 @@ launch: { harness: claude, model: claude-opus-5-5 } # optional (0.30): what th
|
|
|
85
85
|
|
|
86
86
|
Schema: [`soul.schema.json`](soul.schema.json). Which teams a soul joins is the
|
|
87
87
|
deployment's choice (`oats-local.yaml`, [workspaces.md](workspaces.md#teams)),
|
|
88
|
-
and
|
|
89
|
-
(`--
|
|
88
|
+
and yolo and the launch configuration are spawn-time host choices
|
|
89
|
+
(`--yolo`, `--launch-config`, or a launch configuration in
|
|
90
90
|
`oats-local.yaml`), not soul identity. The harness and model are a soul's
|
|
91
91
|
*preference* at most (`launch:`), which each machine overrides and spawn flags
|
|
92
92
|
(`--harness`, `--model`) win over.
|
package/lib/attachments.mjs
CHANGED
|
@@ -147,7 +147,8 @@ export function uploadAttachment({ file, home, server, instance }, io = {}) {
|
|
|
147
147
|
if (!home) throw err("E_BAD_ARGS", "session upload needs --home </absolute/instance> or --server <id> with --instance <name> or --home");
|
|
148
148
|
return { ...receiveAttachment(home, name, bytes, { maxBytes }), name, home: realpathSync(home), source: abs };
|
|
149
149
|
}
|
|
150
|
-
|
|
150
|
+
io = { ...io, serverId: server };
|
|
151
|
+
const route = resolveRoute(server, { instance, home }, "session upload", io);
|
|
151
152
|
const remote = checkRemote(route.target, io);
|
|
152
153
|
// Both lists must carry the token; a probe without the arrays is an old kernel.
|
|
153
154
|
const advertises = (list) => Array.isArray(list) && list.includes("session-upload");
|
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
|
}
|