@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
package/bin/oats.mjs
CHANGED
|
@@ -53,6 +53,7 @@ import { receiveAttachment, uploadAttachment, readStreamBounded, MAX_ATTACHMENT_
|
|
|
53
53
|
|
|
54
54
|
import { observeInstanceGit, diffInstanceFile } from "../lib/instance-git.mjs";
|
|
55
55
|
import { planStop, applyStop, planRetire, resolveInstance as resolveInstanceForCli } from "../lib/instance-lifecycle.mjs";
|
|
56
|
+
import { formatBytes, workRecoveryLines } from "../lib/retire-output.mjs";
|
|
56
57
|
const await_import_lifecycle = () => ({ resolveInstance: resolveInstanceForCli });
|
|
57
58
|
import { homeTarget, soulTarget, isWorkspaceContext, inspectDocument, readinessDocument, policyOf, policySoul, manifestMissingRequires, INSPECT_OPERATIONS_API } from "../lib/instance-inspect.mjs";
|
|
58
59
|
import { readEvents, setWaiting, incarnationOf } from "../lib/instance-events.mjs";
|
|
@@ -155,15 +156,6 @@ const jsonFail = (code, message, details, exit = 1) => { console.log(JSON.string
|
|
|
155
156
|
const jsonOk = (result) => { console.log(JSON.stringify({ schemaVersion: 1, ok: true, result, ...envelopeWarnings() })); };
|
|
156
157
|
// Text mode (or a JSON answer printed before the read): the warning goes to stderr, never stdout.
|
|
157
158
|
process.on("exit", () => { const w = runtimeNameWarning(); if (w && !warningDelivered) process.stderr.write(`oats: warning: ${w.message}\n`); });
|
|
158
|
-
const formatBytes = (n) => n < 1024 ? `${n} B` : n < 1024 ** 2 ? `${(n / 1024).toFixed(1)} KiB` : n < 1024 ** 3 ? `${(n / 1024 ** 2).toFixed(1)} MiB` : `${(n / 1024 ** 3).toFixed(1)} GiB`;
|
|
159
|
-
/** A retire recovery's copied outputs (untracked/ignored or directory work), named with their size. */
|
|
160
|
-
function preservedOutputLines(recovery) {
|
|
161
|
-
const outputs = recovery?.outputs;
|
|
162
|
-
if (!outputs?.paths?.length) return [];
|
|
163
|
-
const shown = outputs.paths.slice(0, 8).map((p) => `${p.path} (${formatBytes(p.bytes)})`);
|
|
164
|
-
const more = outputs.paths.length > 8 ? `, and ${outputs.paths.length - 8} more` : "";
|
|
165
|
-
return [` copied outputs: ${shown.join(", ")}${more} — ${formatBytes(outputs.bytes)} in total`];
|
|
166
|
-
}
|
|
167
159
|
|
|
168
160
|
function shortPath(p) {
|
|
169
161
|
if (!p) return p;
|
|
@@ -2570,11 +2562,7 @@ function retireCmd() {
|
|
|
2570
2562
|
console.log(`Retired ${r.retired} (agent ${r.agent})${r.worktreeRemoved ? ", worktree removed" : ""}`);
|
|
2571
2563
|
// Preserving work and not saying so leaves the operator believing it is gone,
|
|
2572
2564
|
// which is most of the harm of deleting it. Name the classes and the path.
|
|
2573
|
-
for (const
|
|
2574
|
-
console.log(`Work that was not committed has been preserved: ${recovery.classes.join(", ")}`);
|
|
2575
|
-
console.log(` ${recovery.path}${typeof recovery.bytes === "number" ? ` (${formatBytes(recovery.bytes)})` : ""}`);
|
|
2576
|
-
for (const line of preservedOutputLines(recovery)) console.log(line);
|
|
2577
|
-
}
|
|
2565
|
+
for (const line of workRecoveryLines(r)) console.log(line);
|
|
2578
2566
|
for (const w of r.warnings || []) console.log(` WARNING: ${w}`);
|
|
2579
2567
|
if (isSelf) console.log("This window dies in ~8s — say any goodbyes now.");
|
|
2580
2568
|
}
|
|
@@ -3725,11 +3713,7 @@ async function serverRouteCmd() {
|
|
|
3725
3713
|
if (branch) console.error(branch);
|
|
3726
3714
|
}
|
|
3727
3715
|
console.log(`Retired ${r.retired} on ${id}${r.deferred ? " (deferred completion scheduled there)" : ""}${r.rollbackIncomplete ? " — cleanup INCOMPLETE on the server, home retained there" : ""}`);
|
|
3728
|
-
for (const
|
|
3729
|
-
console.log(`Work that was not committed has been preserved on ${target.sshHost}: ${(recovery.classes || []).join(", ")}`);
|
|
3730
|
-
console.log(` ${recovery.path}${typeof recovery.bytes === "number" ? ` (${formatBytes(recovery.bytes)})` : ""}`);
|
|
3731
|
-
for (const line of preservedOutputLines(recovery)) console.log(line);
|
|
3732
|
-
}
|
|
3716
|
+
for (const line of workRecoveryLines(r, { host: target.sshHost })) console.log(line);
|
|
3733
3717
|
if (r.rollbackIncomplete) {
|
|
3734
3718
|
for (const f of r.rollbackIncomplete) console.error(` ${f}`);
|
|
3735
3719
|
const branch = failedSpawnBranchLine(r, r.rollbackIncomplete, { host: target.sshHost });
|
package/docs/capabilities.md
CHANGED
|
@@ -59,7 +59,8 @@ A self-contained package has an `oats.json`:
|
|
|
59
59
|
"hooks": {
|
|
60
60
|
"spawn": "bin/team-chat-hook.mjs spawn",
|
|
61
61
|
"retire": "bin/team-chat-hook.mjs retire"
|
|
62
|
-
}
|
|
62
|
+
},
|
|
63
|
+
"retirement": { "disposable": { "home": [".team-chat", ".team-chat-id-*"] } }
|
|
63
64
|
}
|
|
64
65
|
```
|
|
65
66
|
|
|
@@ -153,7 +154,77 @@ A self-contained package has an `oats.json`:
|
|
|
153
154
|
and nothing is silently dropped. Without `--force` that state fails closed with
|
|
154
155
|
`E_UNIDENTIFIED_INSTANCE_HOME` rather than deleting whatever credentials the
|
|
155
156
|
directory still holds; `--force` removes it and leaves any external state for
|
|
156
|
-
the operator to clean up by hand.
|
|
157
|
+
the operator to clean up by hand. Home entries declared in
|
|
158
|
+
`retirement.disposable.home` (below) go with the home: recovery holds no
|
|
159
|
+
copy of them.
|
|
160
|
+
- `retirement.disposable` declares what retirement treats as the provider's
|
|
161
|
+
own state rather than the instance's work. It is a map with two optional
|
|
162
|
+
keys, `home` and `work`, each an array of strings.
|
|
163
|
+
- `home`: provider-owned state that is not the instance's work, left in
|
|
164
|
+
place until the home is removed, and not copied to recovery. The one
|
|
165
|
+
exception is the preservation of a failed spawn in directory mode, which
|
|
166
|
+
copies the whole home, declared entries included. Each entry
|
|
167
|
+
names top-level entries of the instance home:
|
|
168
|
+
- an exact hidden name, `^\.[A-Za-z0-9_][A-Za-z0-9._-]*$` (`.team-chat`);
|
|
169
|
+
- or a prefix, `^\.[A-Za-z0-9_][A-Za-z0-9._-]*-\*$` (`.team-chat-id-*`),
|
|
170
|
+
meaning every top-level entry whose name starts with the text before
|
|
171
|
+
`*`.
|
|
172
|
+
|
|
173
|
+
Refused:
|
|
174
|
+
- anything that is not one hidden top-level name (`notes`, `.aw/keys`,
|
|
175
|
+
`.a*`);
|
|
176
|
+
- an exact name that is `.oats`, `.agents` or `.claude`, or that starts
|
|
177
|
+
with `.oats-events`, `.oats-stop`, `.oats-restart`, `.oats-rollback`,
|
|
178
|
+
`.oats-agents-md`, `.oats-start` or `.oats-attachments`: the top-level
|
|
179
|
+
entries the kernel itself writes in an instance home;
|
|
180
|
+
- a prefix that starts with `.oats-`.
|
|
181
|
+
|
|
182
|
+
An exact name such as `.oats-aweb` is allowed: it is a provider's
|
|
183
|
+
directory. Entries are matched by name, without following symlinks: a
|
|
184
|
+
declared entry that is a symlink is left out and never followed.
|
|
185
|
+
|
|
186
|
+
A matching entry is left out of the home fingerprint that retire compares
|
|
187
|
+
with the spawn baseline, of the home copy and of that copy's
|
|
188
|
+
verification, before and after the retire hooks. A change to declared
|
|
189
|
+
entries alone is therefore not "changed instance-home bytes" and causes
|
|
190
|
+
no copy. When a recovery is written, its receipt names what was left out
|
|
191
|
+
in `workRecovery.notCopied` (names and owners only).
|
|
192
|
+
|
|
193
|
+
The declaration is recorded at spawn, with its owner, in the home's
|
|
194
|
+
retirement baseline, and retire reads it from there only: never from the
|
|
195
|
+
module copy in the home, from the capability as it is today, or from
|
|
196
|
+
`instance.json`. A running instance is unaffected until it is respawned,
|
|
197
|
+
and a home spawned before its capability declared the entries gains no
|
|
198
|
+
exclusion from a package update: its home is copied whole. A missing or
|
|
199
|
+
invalid baseline means no exclusions either.
|
|
200
|
+
|
|
201
|
+
Exclusion means "not copied" and nothing more. Nothing is removed early:
|
|
202
|
+
the entries stay in the home until the home is removed, and retire hooks
|
|
203
|
+
still see them. An incomplete cleanup, or a copy or verification that
|
|
204
|
+
fails, keeps the home with them. A capability that declares nothing has
|
|
205
|
+
its home state copied with the rest of the home.
|
|
206
|
+
- `work`: relative roots under `work/` that the capability generates. In
|
|
207
|
+
worktree mode the roots are kept out of the "untracked or ignored
|
|
208
|
+
worktree bytes" class, so untracked or ignored bytes under them alone do
|
|
209
|
+
not cause a recovery. They are still copied when a copy is made. Like
|
|
210
|
+
`home`, the roots are recorded at spawn. The other work modes do not use
|
|
211
|
+
them.
|
|
212
|
+
|
|
213
|
+
A malformed `retirement` is refused wherever the manifest is read: member
|
|
214
|
+
discovery (`E_WORKSPACE_SCHEMA`), package manifests (`E_PACKAGE_MANIFEST`)
|
|
215
|
+
and the kernel loader refuse the same manifest, with a JSON pointer:
|
|
216
|
+
`/retirement` (no `disposable` map, or a key other than `disposable`,
|
|
217
|
+
`home` and `work`), `/retirement/disposable/<scope>` (not an array of
|
|
218
|
+
strings) or `/retirement/disposable/home/<i>` (an entry outside the
|
|
219
|
+
grammar):
|
|
220
|
+
|
|
221
|
+
```text
|
|
222
|
+
capability <id> manifest retirement.disposable.home entry "<value>" must name one hidden top-level home entry (".name", or ".prefix-*")
|
|
223
|
+
capability <id> manifest retirement.disposable.home entry "<value>" covers a kernel-owned home entry
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
This kernel reads the field. What retire does with the entries is in
|
|
227
|
+
[souls-and-instances.md](souls-and-instances.md#retire).
|
|
157
228
|
- `requires` declares what must exist before the capability works. Two kinds:
|
|
158
229
|
- a **host command** (`command`), satisfied by a binary on `PATH`;
|
|
159
230
|
- a **harness package** (`harness` + `package`, optionally `marketplace`),
|
|
@@ -293,6 +293,44 @@
|
|
|
293
293
|
"type": "string",
|
|
294
294
|
"pattern": "^[A-Z][A-Z0-9]*_$"
|
|
295
295
|
}
|
|
296
|
+
},
|
|
297
|
+
"retirement": {
|
|
298
|
+
"type": "object",
|
|
299
|
+
"description": "What retirement treats as the provider's own state rather than the instance's work.",
|
|
300
|
+
"required": ["disposable"],
|
|
301
|
+
"properties": {
|
|
302
|
+
"disposable": {
|
|
303
|
+
"type": "object",
|
|
304
|
+
"properties": {
|
|
305
|
+
"home": {
|
|
306
|
+
"type": "array",
|
|
307
|
+
"description": "Provider-owned state that is not the instance's work, left in place until the home is removed, and not copied to recovery (the preservation of a failed spawn in directory mode is the exception: it copies the whole home, declared entries included). Each entry names top-level entries of the instance home: one hidden name (.aw) or a prefix (.aweb-identity-*, every top-level name that starts with the text before the *). Recorded at spawn: a running instance is unaffected until it is respawned.",
|
|
308
|
+
"items": {
|
|
309
|
+
"type": "string",
|
|
310
|
+
"pattern": "^\\.[A-Za-z0-9_][A-Za-z0-9._-]*(?:-\\*)?$",
|
|
311
|
+
"not": {
|
|
312
|
+
"description": "Home entries the kernel owns cannot be declared.",
|
|
313
|
+
"anyOf": [
|
|
314
|
+
{"pattern": "^\\.(?:oats|agents|claude)$"},
|
|
315
|
+
{"pattern": "^\\.oats-(?:events|stop|restart|rollback|agents-md|start|attachments)"},
|
|
316
|
+
{"pattern": "^\\.oats-.*\\*$"}
|
|
317
|
+
]
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
},
|
|
321
|
+
"work": {
|
|
322
|
+
"type": "array",
|
|
323
|
+
"description": "Relative roots under work/ that a worktree instance's capability generates. Their untracked or ignored bytes do not count as the instance's untracked or ignored worktree bytes; they are still copied when a recovery copy is made.",
|
|
324
|
+
"items": {
|
|
325
|
+
"type": "string",
|
|
326
|
+
"minLength": 1
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
"additionalProperties": false
|
|
331
|
+
}
|
|
332
|
+
},
|
|
333
|
+
"additionalProperties": false
|
|
296
334
|
}
|
|
297
335
|
},
|
|
298
336
|
"additionalProperties": false
|
package/docs/desktop-cli-api.md
CHANGED
|
@@ -2457,6 +2457,25 @@ oats retire <instance> --plan [--home <abs>] [--dir <d>] --json
|
|
|
2457
2457
|
`unestablished`, with a `note` saying why, and retire refuses with
|
|
2458
2458
|
`E_RUNTIME_ENDPOINT_UNKNOWN`, `--force` included. `notes` repeats either
|
|
2459
2459
|
case. Read an unknown `state` as not idle.
|
|
2460
|
+
- `notes` says what a retire would copy to recovery (0.42), in up to two
|
|
2461
|
+
strings read from the home's retirement baseline and the work mode, without
|
|
2462
|
+
hashing anything:
|
|
2463
|
+
|
|
2464
|
+
```text
|
|
2465
|
+
recovery: the home is copied to /w/agents/dev/instances/.oats-retirement/recovery before the home is removed, when it changed since spawn; not copied: .aw, .aweb-identity, .aweb-identity-*, .oats-aweb (oats.aweb)
|
|
2466
|
+
recovery: uncommitted worktree state is copied there too
|
|
2467
|
+
```
|
|
2468
|
+
|
|
2469
|
+
The first is always present. Its `; not copied: …` tail is there only when
|
|
2470
|
+
the baseline records declared home entries
|
|
2471
|
+
(`retirement.disposable.home`): `not copied: <roots> (<capability>)`, the
|
|
2472
|
+
roots as written in the manifest, in the order the baseline stores them
|
|
2473
|
+
(sorted by capability, then root), one group per capability, groups
|
|
2474
|
+
separated by `; `. At most 16 roots are listed, then `, and N more`.
|
|
2475
|
+
The second is the line above in worktree mode, `recovery: work/ is copied
|
|
2476
|
+
there when it is not empty` in directory mode, and absent in checkout,
|
|
2477
|
+
attached and workspace modes. They are strings in `notes`: no other key
|
|
2478
|
+
changes, and `planRevision` is unaffected.
|
|
2460
2479
|
|
|
2461
2480
|
Plain `retire` keeps a worktree-mode instance's work: the worktree is moved
|
|
2462
2481
|
(`git worktree move`) to `<deployment>/.agents/worktrees/<repo>/<branch>` (a
|
|
@@ -2472,8 +2491,12 @@ A first retire prints the **raw receipt**, not an envelope:
|
|
|
2472
2491
|
{"retired":"dev-1","agent":"dev",
|
|
2473
2492
|
"retention":{"worktree":"retained","movedTo":"/w/.agents/worktrees/one/feat-x","branch":"feat/x","detachedAt":null,"recordedBranch":"agents/dev-1"},
|
|
2474
2493
|
"worktreeRemoved":false,"branchDeleted":false,"removedDir":true,
|
|
2475
|
-
"workRecovery":{"path":"/w
|
|
2476
|
-
"
|
|
2494
|
+
"workRecovery":{"path":"/w/agents/dev/instances/.oats-retirement/recovery/dev-1-AbC123",
|
|
2495
|
+
"classes":["changed instance-home bytes","untracked or ignored worktree bytes"],"bytes":48444211,
|
|
2496
|
+
"home":{"paths":[{"path":".oats/","bytes":874696},{"path":".agents/","bytes":141312},{"path":"notes/","bytes":2048},{"path":"STATE.md","bytes":512}],"bytes":1018568},
|
|
2497
|
+
"outputs":{"paths":[{"path":"scratch/","bytes":1258291},{"path":"note.txt","bytes":12}],"bytes":1258303},
|
|
2498
|
+
"notCopied":[{"scope":"home","path":".aw","owner":"oats.aweb"}],
|
|
2499
|
+
"afterHooks":{"home":true,"work":false}},
|
|
2477
2500
|
"childrenStopped":[{"instance":"dev-1-child","home":"/w/agents/dev/instances/dev-1-child","ok":true,"stopped":false,"alreadyIdle":true}],
|
|
2478
2501
|
"planRevision":"4e5f6a7b8c9d0e1f2a3b4c5d","idempotencyKey":"r1","replayed":false}
|
|
2479
2502
|
```
|
|
@@ -2512,9 +2535,67 @@ A first retire prints the **raw receipt**, not an envelope:
|
|
|
2512
2535
|
retire (hooks run, a recovery copied), and neither says that one happened:
|
|
2513
2536
|
an error here is not proof that nothing happened, nor that a recovery
|
|
2514
2537
|
exists.
|
|
2515
|
-
- `workRecovery
|
|
2516
|
-
|
|
2517
|
-
|
|
2538
|
+
- `workRecovery`: `{path, classes, bytes, home, outputs?, repoCopy?,
|
|
2539
|
+
notCopied?, afterHooks?}`, present when a recovery was written. One retire
|
|
2540
|
+
writes at most one recovery directory, and `path` is that directory.
|
|
2541
|
+
- `classes` is the union of the classes seen before and after the retire
|
|
2542
|
+
hooks, in first-seen order. `bytes` covers the whole directory,
|
|
2543
|
+
`after-hooks/` included.
|
|
2544
|
+
- `home`, `outputs` and `repoCopy` describe the pre-hook snapshot (the
|
|
2545
|
+
top-level `home/`, `repo/` or `work/`) and are not rewritten by the
|
|
2546
|
+
post-hook pass. So `repoCopy.copied: false` (a home-only snapshot) can
|
|
2547
|
+
appear with `afterHooks.work: true`: the repository copy is then under
|
|
2548
|
+
`after-hooks/repo/` only. A recovery first written after the hooks (the
|
|
2549
|
+
instance was clean before them) has no `after-hooks/`, and those keys
|
|
2550
|
+
describe that one snapshot.
|
|
2551
|
+
- `home`: `{paths: [{path, bytes}], bytes}`. The top-level entries of the
|
|
2552
|
+
`home/` snapshot, largest first, a directory ending in `/`. Always
|
|
2553
|
+
present when a recovery is written.
|
|
2554
|
+
- `outputs`: `{paths: [{path, bytes}], bytes}` names what was copied
|
|
2555
|
+
beyond tracked state, largest first.
|
|
2556
|
+
- `notCopied`: `[{scope, path, owner}]`, sorted by `path`. The home
|
|
2557
|
+
entries that existed at any point of the retire (before or after the
|
|
2558
|
+
hooks) and were left out by a capability's `retirement.disposable.home`
|
|
2559
|
+
declaration ([capabilities.md](capabilities.md#manifest)), by their real
|
|
2560
|
+
names: a prefix that matches several entries lists each one, and a
|
|
2561
|
+
declared entry that does not exist is not listed. `owner` is the
|
|
2562
|
+
declaring capability; when two declare the same entry it is the first in
|
|
2563
|
+
capability-name order. Each entry has exactly these three keys: names and
|
|
2564
|
+
owners only, no sizes, hashes, modes or contents of what was left out.
|
|
2565
|
+
`scope` is always `"home"` in this release. Present only when there is
|
|
2566
|
+
at least one.
|
|
2567
|
+
- `afterHooks`: `{home: boolean, work: boolean}`, saying which parts were
|
|
2568
|
+
copied again under `after-hooks/`: `after-hooks/home/` when a retire hook
|
|
2569
|
+
changed the home (its bytes and permission bits, the kernel's own
|
|
2570
|
+
records included), and `after-hooks/repo/` (worktree mode) or
|
|
2571
|
+
`after-hooks/work/` (directory mode) unless the work is proven unchanged
|
|
2572
|
+
after the hooks: a directory by its bytes and bits, a worktree by its Git
|
|
2573
|
+
state and the bytes and bits of its files
|
|
2574
|
+
([souls and instances](souls-and-instances.md#retire)). A worktree that
|
|
2575
|
+
holds a repository is never proven unchanged: for it `work: true` says
|
|
2576
|
+
that a work copy was made after the hooks, not that a hook changed the
|
|
2577
|
+
work. `work` is also `true` when the pre-hook snapshot held the home
|
|
2578
|
+
only and the work was copied for the first time after the hooks, moved
|
|
2579
|
+
or not, because something beyond the home was there to preserve. The
|
|
2580
|
+
home is not copied again because the work is.
|
|
2581
|
+
Each part is whole and verified, not a delta, but `after-hooks/` is not
|
|
2582
|
+
a complete picture of the instance after the hooks: with `home: false`
|
|
2583
|
+
the recovery's home is the pre-hook one, and it holds the kernel's own
|
|
2584
|
+
records (the event log, the stop and restart receipts, listed in
|
|
2585
|
+
[souls and instances](souls-and-instances.md#retire)) as of then.
|
|
2586
|
+
Present only when that directory was written.
|
|
2587
|
+
- `workRecoveries` is no longer emitted. An older kernel on a server may
|
|
2588
|
+
still send `workRecoveries[]` beside `workRecovery` (one `{path, classes,
|
|
2589
|
+
bytes, outputs?, repoCopy?}` per recovery directory it wrote), so a
|
|
2590
|
+
reader must keep accepting it. A kernel before 0.42 sends no `home`,
|
|
2591
|
+
`notCopied` or `afterHooks`.
|
|
2592
|
+
- `recovery.json` inside the directory stays `version: 1`. It carries
|
|
2593
|
+
`phase` (`"before-hooks"`, then `"complete"` once the post-hook check has
|
|
2594
|
+
concluded), `home` and, when they apply, `notCopied` and `afterHooks`.
|
|
2595
|
+
`phase` is not in the receipt: a receipt is produced only once that check
|
|
2596
|
+
has concluded. A retried retire writes its own recovery and its receipt
|
|
2597
|
+
names only that one
|
|
2598
|
+
([souls-and-instances.md](souls-and-instances.md#retire)).
|
|
2518
2599
|
- When they apply: `rollbackIncomplete` and `retainedHome` (cleanup
|
|
2519
2600
|
incomplete, home kept, exit 1), `forcedIncomplete`, `relinked`,
|
|
2520
2601
|
`capabilityMeta`, `warnings`, `wakeSchedulesRemoved`.
|
|
@@ -2535,7 +2616,9 @@ A first retire prints the **raw receipt**, not an envelope:
|
|
|
2535
2616
|
|
|
2536
2617
|
Refusals (envelopes): `E_PLAN_STALE`, `E_CHILDREN_RUNNING`,
|
|
2537
2618
|
`E_WORK_PRESERVATION_FAILED` (the home is kept; retry or
|
|
2538
|
-
`--discard-worktree`), `
|
|
2619
|
+
`--discard-worktree`), `E_WORK_INSPECTION_FAILED` (the home is kept; the
|
|
2620
|
+
message names the entry or the state that could not be read),
|
|
2621
|
+
`E_SESSION_UNKNOWN`, `E_AMBIGUOUS_INSTANCE`,
|
|
2539
2622
|
`E_NO_ROOT`, `E_LIFECYCLE_FAILED`. A recovery whose Git status disagrees with
|
|
2540
2623
|
the source's carries `details: {home, statusDisagreement: {repo, rows: [{path,
|
|
2541
2624
|
source, recovery}], total}}` (the first 10 paths). Usage errors are text on
|
package/docs/desktop.md
CHANGED
|
@@ -77,16 +77,26 @@ Opened from Finder or the Dock, an app inherits launchd's PATH
|
|
|
77
77
|
the CLI's `#!/usr/bin/env node`. At startup the Desktop therefore runs your
|
|
78
78
|
login shell once (`$SHELL -ilc`, 3 s timeout) and puts its PATH in front of
|
|
79
79
|
the inherited one, so the CLI probe, every `oats` call, a CLI picked with
|
|
80
|
-
**Choose oats…**, and the
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
**Choose oats…**, and the terminals the Desktop starts all run with your
|
|
81
|
+
shell's PATH. Only PATH is taken from the shell, never the rest of its
|
|
82
|
+
environment. The OATS tmux server is different: when a spawn or a start the
|
|
83
|
+
Desktop runs has to start it, the CLI gives it your whole login environment,
|
|
84
|
+
read from your login shell
|
|
85
|
+
([execution-targets.md](execution-targets.md#the-servers-start-environment)),
|
|
86
|
+
and falls back to the environment the Desktop gave the CLI (with this PATH)
|
|
87
|
+
only when that cannot be read, saying so on stderr. The shell is started with your environment, without what the
|
|
83
88
|
Desktop or its packaging added to its own (on the AppImage, the entries under
|
|
84
89
|
its mount), like every other program the Desktop starts. If the shell fails, times out or prints no PATH, the
|
|
85
90
|
inherited PATH stays: the backend's `/api/cli` reports `pathSource`
|
|
86
91
|
(`login-shell` or `inherited`), `pathError` (why, or `null`) and
|
|
87
92
|
`probePath` (the PATH the probe used), and the reason is logged at startup.
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
An agent's pane takes its tmux session's or server's PATH, whoever opens its
|
|
94
|
+
window, and its harness is looked up there
|
|
95
|
+
([execution-targets.md](execution-targets.md#the-servers-start-environment)):
|
|
96
|
+
a window the Desktop opens on a server that already runs gets that server's
|
|
97
|
+
PATH, not the Desktop's. A tmux server that was already running keeps its own
|
|
98
|
+
environment; restart it if its sessions should get the new PATH, which ends
|
|
99
|
+
its sessions: `tmux -L oats kill-server` for the OATS tmux server, where instances run, and `tmux kill-server` for your default server, where an instance started by an earlier kernel may still be.
|
|
90
100
|
|
|
91
101
|
## Opening a workspace
|
|
92
102
|
|