@awebai/oats 0.22.14 → 0.22.17
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 +678 -23
- package/capabilities/oats-okf/bin/oats-okf.mjs +47 -8
- package/capabilities/oats-okf/oats.json +18 -3
- package/docs/capabilities.md +7 -0
- package/docs/capability-manifest.schema.json +32 -0
- package/docs/design/2026-09-07-desktop-souls-capabilities.md +50 -0
- package/docs/design/operations-contract.md +123 -0
- package/docs/release-notes/v0.22.15.md +48 -0
- package/docs/release-notes/v0.22.16.md +53 -0
- package/docs/release-notes/v0.22.17.md +25 -0
- package/docs/schedules.md +9 -0
- package/lib/core.mjs +37 -2
- package/lib/schedule.mjs +32 -8
- package/lib/servers.mjs +32 -1
- package/package-catalog.json +29 -8
- package/package.json +1 -1
|
@@ -23,29 +23,44 @@
|
|
|
23
23
|
* OATS_TASK (spawn), OATS_REPO/OATS_BRANCH/OATS_WORK (spawn), OATS_META (retire).
|
|
24
24
|
* Output: JSON { meta, brief, warning } on stdout. Failures warn, never block.
|
|
25
25
|
*/
|
|
26
|
-
import { existsSync, mkdirSync, mkdtempSync, writeFileSync, readFileSync, readdirSync, realpathSync, rmSync } from "node:fs";
|
|
26
|
+
import { existsSync, mkdirSync, mkdtempSync, writeFileSync, readFileSync, readdirSync, realpathSync, rmSync, writeSync } from "node:fs";
|
|
27
27
|
import { join, isAbsolute, dirname } from "node:path";
|
|
28
28
|
import { tmpdir } from "node:os";
|
|
29
29
|
import { execFile, spawnSync } from "node:child_process";
|
|
30
30
|
import { reclaimHarvestBranch } from "../lib/harvest-branch.mjs";
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
// Every answer leaves through here: written whole, then exit. stdout is a
|
|
33
|
+
// pipe when the kernel's operation runner, a Desktop or a test reads it, and
|
|
34
|
+
// on macOS Node writes to a pipe asynchronously: process.exit right after
|
|
35
|
+
// process.stdout.write drops whatever has not left Node's buffer (64 KiB,
|
|
36
|
+
// so a view of one long STATE.md arrived cut). fs.writeSync on fd 1 blocks
|
|
37
|
+
// until the OS has the bytes; EAGAIN (a non-blocking fd whose reader is
|
|
38
|
+
// slower than us) is retried after a short wait. The exit stays synchronous,
|
|
39
|
+
// which the call sites below rely on to end their flow.
|
|
40
|
+
const emit = (text, code) => {
|
|
41
|
+
const buf = Buffer.from(text, "utf8");
|
|
42
|
+
for (let off = 0; off < buf.length;) {
|
|
43
|
+
try { off += writeSync(1, buf, off, buf.length - off); }
|
|
44
|
+
catch (e) { if (e.code !== "EAGAIN") throw e; Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 5); }
|
|
45
|
+
}
|
|
46
|
+
process.exit(code);
|
|
47
|
+
};
|
|
48
|
+
const out = (o) => emit(JSON.stringify(o) + "\n", 0);
|
|
33
49
|
const warn = (m) => out({ warning: `oats-okf: ${String(m).slice(0, 300)}` });
|
|
34
50
|
// A reported failure must not exit 0: callers and hooks read the status.
|
|
35
|
-
const warnFail = (m) =>
|
|
51
|
+
const warnFail = (m) => emit(JSON.stringify({ warning: `oats-okf: ${String(m).slice(0, 300)}` }) + "\n", 1);
|
|
36
52
|
|
|
37
53
|
// Desktop CLI API v1: `oats okf harvest --json` emits EXACTLY ONE envelope
|
|
38
54
|
// object on stdout — {schemaVersion:1,ok,result|error} — and a nonzero exit
|
|
39
55
|
// on failure. Ordinary (non---json) output keeps the hook JSON shape above.
|
|
40
56
|
const JSON_MODE = process.argv.includes("--json");
|
|
41
|
-
const jsonOk = (result) =>
|
|
42
|
-
const jsonFail = (code, message) =>
|
|
57
|
+
const jsonOk = (result) => emit(JSON.stringify({ schemaVersion: 1, ok: true, result }) + "\n", 0);
|
|
58
|
+
const jsonFail = (code, message) => emit(JSON.stringify({ schemaVersion: 1, ok: false, error: { code, message: String(message).slice(0, 300) } }) + "\n", 1);
|
|
43
59
|
|
|
44
60
|
// --help/-h never runs a command here either (the kernel answers it from the
|
|
45
61
|
// manifest since 0.22.6; this keeps an older kernel from spawning a harvester).
|
|
46
62
|
if (process.argv.slice(2).some((a) => a === "--help" || a === "-h")) {
|
|
47
|
-
|
|
48
|
-
process.exit(0);
|
|
63
|
+
emit("oats okf harvest [--json] [--from-record] [--force] promote this instance's pending notes (and record windows) into its soul by spawning a memory-harvest worker; --help never runs it\noats okf inspect [--json] answer this instance's working knowledge (STATE.md, log.md, pending notes) as labeled documents\n", 0);
|
|
49
64
|
}
|
|
50
65
|
const event = process.env.OATS_EVENT || process.argv[2];
|
|
51
66
|
const instance = process.env.OATS_INSTANCE;
|
|
@@ -473,6 +488,30 @@ _(the single next action — keep this current; a fresh session on any model res
|
|
|
473
488
|
if (JSON_MODE) jsonFail(e.code || "E_HARVEST_FAILED", `harvest spawn failed (notes are safe on disk): ${e.message || e}`);
|
|
474
489
|
warnFail(`harvest spawn failed (notes are safe on disk): ${e.message || e}`);
|
|
475
490
|
}
|
|
491
|
+
} else if (event === "inspect") {
|
|
492
|
+
// VIEW OPERATION (kernel contract, docs/design/operations-contract.md):
|
|
493
|
+
// the instance's working knowledge as labeled documents, read from the
|
|
494
|
+
// home this command runs in. Text only; paths are provenance for the
|
|
495
|
+
// reader. Answers the JSON-v1 envelope regardless of --json.
|
|
496
|
+
try {
|
|
497
|
+
const CAP = 256 * 1024;
|
|
498
|
+
const doc = (label, file) => {
|
|
499
|
+
if (!existsSync(file)) return null;
|
|
500
|
+
const bytes = readFileSync(file);
|
|
501
|
+
const truncated = bytes.length > CAP;
|
|
502
|
+
let text = truncated ? bytes.subarray(0, CAP).toString("utf8") : bytes.toString("utf8");
|
|
503
|
+
if (truncated && text.endsWith("\uFFFD")) text = text.slice(0, -1);
|
|
504
|
+
return { label, kind: "markdown", path: file, text, ...(truncated ? { truncated: true, bytes: bytes.length } : {}) };
|
|
505
|
+
};
|
|
506
|
+
const documents = [doc("Working state (STATE.md)", join(home, "STATE.md")), doc("Log (log.md)", join(home, "log.md"))].filter(Boolean);
|
|
507
|
+
const notesDir = join(home, "notes");
|
|
508
|
+
const notes = existsSync(notesDir) ? readdirSync(notesDir).filter((f) => f.endsWith(".md")).sort() : [];
|
|
509
|
+
for (const f of notes) { const d = doc(`Pending note: ${f}`, join(notesDir, f)); if (d) documents.push(d); }
|
|
510
|
+
const summary = documents.length ? `${documents.length} document${documents.length === 1 ? "" : "s"}: ${existsSync(join(home, "STATE.md")) ? "state" : "no state"}, ${existsSync(join(home, "log.md")) ? "log" : "no log"}, ${notes.length} pending note${notes.length === 1 ? "" : "s"}` : "no working memory in this home yet (STATE.md, log.md and notes/ are written by the instance)";
|
|
511
|
+
jsonOk({ summary, documents });
|
|
512
|
+
} catch (e) {
|
|
513
|
+
jsonFail("E_INSPECT_FAILED", e.message || e);
|
|
514
|
+
}
|
|
476
515
|
} else if (event === "retire") {
|
|
477
516
|
// Retirement is intentionally a no-op for knowledge (for now): promotion happens
|
|
478
517
|
// continuously via agent-initiated harvest. Uncommitted notes die with the home —
|
|
@@ -480,5 +519,5 @@ _(the single next action — keep this current; a fresh session on any model res
|
|
|
480
519
|
// before finishing.
|
|
481
520
|
out({ meta: {} });
|
|
482
521
|
} else {
|
|
483
|
-
warn(`unknown event "${event}" (expected soul-scaffold|spawn|retire)`);
|
|
522
|
+
warn(`unknown event "${event}" (expected soul-scaffold|spawn|retire|harvest|inspect)`);
|
|
484
523
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.okf",
|
|
3
3
|
"command": "okf",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.6.1",
|
|
5
5
|
"compatibility": {
|
|
6
6
|
"oats": ">=0.22.3"
|
|
7
7
|
},
|
|
8
8
|
"layer": "knowledge",
|
|
9
|
-
"description": "Knowledge layer via OKF: soul bundles, instance memory (STATE.md/log.md/notes/), continuous post-commit harvest into the soul (commit, PR, or direct-edit for local souls), craft + memory skills, validator.",
|
|
9
|
+
"description": "Knowledge layer via OKF: soul bundles, instance memory (STATE.md/log.md/notes/), continuous post-commit harvest into the soul (commit, PR, or direct-edit for local souls), craft + memory skills, validator; declares the operations a GUI or schedule may run (inspect, harvest).",
|
|
10
10
|
"requires": [],
|
|
11
11
|
"settings": {
|
|
12
12
|
"harvest-runtime": {
|
|
@@ -29,11 +29,26 @@
|
|
|
29
29
|
"skills"
|
|
30
30
|
],
|
|
31
31
|
"commands": {
|
|
32
|
-
"harvest": "bin/oats-okf.mjs harvest"
|
|
32
|
+
"harvest": "bin/oats-okf.mjs harvest",
|
|
33
|
+
"inspect": "bin/oats-okf.mjs inspect"
|
|
33
34
|
},
|
|
34
35
|
"inject": "injects/okf.md",
|
|
35
36
|
"hooks": {
|
|
36
37
|
"soul-scaffold": "bin/oats-okf.mjs soul-scaffold",
|
|
37
38
|
"spawn": "bin/oats-okf.mjs spawn"
|
|
39
|
+
},
|
|
40
|
+
"operations": {
|
|
41
|
+
"inspect": {
|
|
42
|
+
"kind": "view",
|
|
43
|
+
"command": "inspect",
|
|
44
|
+
"context": "home",
|
|
45
|
+
"description": "Show this instance's working knowledge: STATE.md, log.md and pending notes"
|
|
46
|
+
},
|
|
47
|
+
"harvest": {
|
|
48
|
+
"kind": "action",
|
|
49
|
+
"command": "harvest",
|
|
50
|
+
"context": "home",
|
|
51
|
+
"description": "Promote this instance's pending notes (or captured record) into its soul by spawning a memory-harvest worker"
|
|
52
|
+
}
|
|
38
53
|
}
|
|
39
54
|
}
|
package/docs/capabilities.md
CHANGED
|
@@ -503,3 +503,10 @@ The source packages live under `capabilities/`. Acquired packages live under
|
|
|
503
503
|
`<level>/.agents/capabilities/installed/` (gitignored, restorable); packages
|
|
504
504
|
authored at a scope live under `<level>/.agents/capabilities/owned/`
|
|
505
505
|
(committed where the scope is a git repo). Within one scope `owned/` overrides `installed/` on ID collision.
|
|
506
|
+
|
|
507
|
+
## Operations a capability declares
|
|
508
|
+
|
|
509
|
+
A manifest may declare `operations` (named actions or views delegating to
|
|
510
|
+
its own commands) that a GUI or a schedule invokes through `oats operation
|
|
511
|
+
run <layer>:<name>`; `oats inspect --json` reports them with availability.
|
|
512
|
+
See [docs/design/operations-contract.md](design/operations-contract.md).
|
|
@@ -150,6 +150,38 @@
|
|
|
150
150
|
"minLength": 1
|
|
151
151
|
}
|
|
152
152
|
},
|
|
153
|
+
"operations": {
|
|
154
|
+
"description": "Named operations a GUI or scheduler may invoke through `oats operation run`, each delegating to one of this manifest's commands. kind \"view\" answers { documents: [{label, kind, path, text}] }; \"action\" (default) runs something. context \"home\" (default) runs in an instance home, \"scope\" in the config scope.",
|
|
155
|
+
"type": "object",
|
|
156
|
+
"propertyNames": {
|
|
157
|
+
"pattern": "^[a-z][a-z0-9-]*$"
|
|
158
|
+
},
|
|
159
|
+
"additionalProperties": {
|
|
160
|
+
"type": "object",
|
|
161
|
+
"required": ["command"],
|
|
162
|
+
"additionalProperties": false,
|
|
163
|
+
"properties": {
|
|
164
|
+
"command": { "type": "string", "minLength": 1 },
|
|
165
|
+
"kind": { "enum": ["action", "view"] },
|
|
166
|
+
"context": { "enum": ["home", "scope"] },
|
|
167
|
+
"description": { "type": "string" },
|
|
168
|
+
"args": {
|
|
169
|
+
"type": "array",
|
|
170
|
+
"items": {
|
|
171
|
+
"type": "object",
|
|
172
|
+
"required": ["name"],
|
|
173
|
+
"additionalProperties": false,
|
|
174
|
+
"properties": {
|
|
175
|
+
"name": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
|
|
176
|
+
"flag": { "type": "string", "pattern": "^--[a-z][a-z0-9-]*$" },
|
|
177
|
+
"required": { "type": "boolean" },
|
|
178
|
+
"description": { "type": "string" }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
},
|
|
153
185
|
"hooks": {
|
|
154
186
|
"type": "object",
|
|
155
187
|
"properties": {
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Souls and capabilities in Desktop
|
|
2
|
+
|
|
3
|
+
Selecting a soul opens its details. Launch and Schedule are explicit actions;
|
|
4
|
+
Quick Open and Enter inspect instead of launching. The existing local Files
|
|
5
|
+
browser remains available. It is separate from provider knowledge inspection.
|
|
6
|
+
|
|
7
|
+
The inspector reads the kernel's `oats inspect --json` answer on selection,
|
|
8
|
+
Refresh, or after an explicit mutation. The roster's eight-second poll never
|
|
9
|
+
rescans capabilities or replaces an editor. Workspace changes invalidate old
|
|
10
|
+
responses. Same-named souls are addressed by name plus agents root; instances
|
|
11
|
+
are addressed by their exact home.
|
|
12
|
+
|
|
13
|
+
The Capabilities button inspects workspace defaults, with a member-scope
|
|
14
|
+
selector where a workspace contains several roots. Installed version, source
|
|
15
|
+
and health are separate from effective activation, provenance and settings.
|
|
16
|
+
Enable, explicit disable and return to inheritance call `oats use`; layer-wide
|
|
17
|
+
disable is distinct from excluding a single capability, and is available only
|
|
18
|
+
when inspecting a workspace or member scope, never an individual soul. These changes apply to
|
|
19
|
+
future instances. Existing homes retain their captured bindings and settings.
|
|
20
|
+
|
|
21
|
+
Authored souls expose only the fields the kernel reports as editable. Desktop
|
|
22
|
+
edits runtime, model, backend, YOLO, description and instructions through
|
|
23
|
+
`oats soul set`. Packaged souls remain read-only and explain where their source
|
|
24
|
+
must be changed. Instructions use a private temporary file; the CLI owns remote
|
|
25
|
+
transfer and mutation. Desktop does not modify YAML or resolve capabilities.
|
|
26
|
+
|
|
27
|
+
An instance's action menu opens Knowledge & capabilities. The kernel supplies
|
|
28
|
+
its snapshot, differences from current configuration, and declared provider
|
|
29
|
+
operations. View operations return labeled documents; Desktop renders their
|
|
30
|
+
text and treats remote paths as provenance, never as local files to open. Actions
|
|
31
|
+
without required arguments can run here. Operations requiring arguments explain
|
|
32
|
+
that they need the CLI. No provider is assumed to support harvesting.
|
|
33
|
+
|
|
34
|
+
Schedules store an operation address and exact home with the kernel's generic
|
|
35
|
+
`operation` job kind. The form discovers declared, available actions on demand.
|
|
36
|
+
The server verifies that declaration again at save time, and the kernel resolves
|
|
37
|
+
and validates the provider at execution time. Existing command schedules remain
|
|
38
|
+
visible and runnable, but the GUI does not reverse-parse their argv to edit them.
|
|
39
|
+
|
|
40
|
+
Every capability request requires an exact advertised workspace. Remote requests
|
|
41
|
+
require its saved server registration and the CLI's operations API feature; all
|
|
42
|
+
routing stays in OATS. The consumer never performs SSH or silently substitutes a
|
|
43
|
+
local workspace. The proxy allows the CLI's bounded operation to return its
|
|
44
|
+
receipt before timing out.
|
|
45
|
+
|
|
46
|
+
Validation: scope/identity and remote-route refusal; private instructions and
|
|
47
|
+
failure cleanup; declared schedule actions; stale inspection/save responses;
|
|
48
|
+
provider text rendering; explicit launch and existing runtime flows. Native
|
|
49
|
+
Electron rendering is checked with a temporary fixture frame in the existing
|
|
50
|
+
GUI, without starting a model, agent, or another application instance.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Provider operations and inspection contract
|
|
2
|
+
|
|
3
|
+
Implemented 2026-09-07 on the existing capability engine. The kernel
|
|
4
|
+
resolves providers; a GUI reads one answer and calls the commands below.
|
|
5
|
+
Nothing here names a provider: what a knowledge, messaging or tasks
|
|
6
|
+
capability offers is what its manifest declares.
|
|
7
|
+
|
|
8
|
+
## Manifest: operations
|
|
9
|
+
|
|
10
|
+
```json
|
|
11
|
+
"operations": {
|
|
12
|
+
"harvest": { "kind": "action", "command": "harvest", "context": "home", "description": "Promote this instance's notes into its soul" },
|
|
13
|
+
"inspect": { "kind": "view", "command": "inspect", "context": "home", "description": "Show this instance's working knowledge" }
|
|
14
|
+
}
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`command` names one of the manifest's `commands`. `kind` is `action`
|
|
18
|
+
(default) or `view`. `context` is `home` (default; runs in an instance
|
|
19
|
+
home) or `scope` (runs in the config scope). Optional `args` declare
|
|
20
|
+
`{name, flag, required, description}`; the runner passes `--arg name=value`
|
|
21
|
+
pairs as those flags and refuses unknown or missing required ones. A view
|
|
22
|
+
must answer `{ documents: [{ label, kind: "markdown"|"text", path?, text? }],
|
|
23
|
+
summary? }`; the kernel validates that shape and relays it. Paths are
|
|
24
|
+
provenance for the reader; the kernel reads nothing for a view. Validation
|
|
25
|
+
happens when the manifest loads (`docs/capability-manifest.schema.json`).
|
|
26
|
+
|
|
27
|
+
## oats inspect
|
|
28
|
+
|
|
29
|
+
`oats inspect [--dir <scope>] [--soul <name> [--agents-root <abs>]] [--home
|
|
30
|
+
<abs>] [--server <id>] --json` answers, in one envelope: `scope` (context,
|
|
31
|
+
workspace, team, chain, agentsRoots); `souls` (persistent, local and
|
|
32
|
+
packaged, each with runtime defaults, `editable {fields, instructions,
|
|
33
|
+
reason}`, instances, and for the selected soul its `instructions {file,
|
|
34
|
+
text, sha256, bytes, truncated, error}` capped at 256 KiB of bytes on a
|
|
35
|
+
character boundary); `layers` (effective provider per layer); `capabilities`
|
|
36
|
+
(installed state and `health` from the package engine, `missingRequires`,
|
|
37
|
+
and separately `activation {enabled, source, target, level, provenance,
|
|
38
|
+
settings, declaredAt}` plus `operations` with `available` and a `reason`);
|
|
39
|
+
`knowledge` (the effective knowledge provider's operations); `snapshot` and
|
|
40
|
+
`currentConfig` with `--home`; `problems`.
|
|
41
|
+
|
|
42
|
+
A `--home` answer is authoritative for that home: activation, settings,
|
|
43
|
+
layers and operation availability come from the home's captured bindings
|
|
44
|
+
with the currently acquired manifests and current trust, marked `source:
|
|
45
|
+
"snapshot"`; the live config is `currentConfig`, and `snapshot.drift`
|
|
46
|
+
lists activation, settings and integrity differences. The home selects its
|
|
47
|
+
own soul under its own agents root (its recorded work repository may be
|
|
48
|
+
another repository); `--dir`, if given, must be the recorded repository or
|
|
49
|
+
the workspace of that root, and `--agents-root` must be that root
|
|
50
|
+
(`E_HOME_MISMATCH`). Without a home, `--dir` is the config context and
|
|
51
|
+
same-named souls under two member roots need `--agents-root`
|
|
52
|
+
(`E_SOUL_AMBIGUOUS`). The ambient `PI_AGENTS_ROOT` never redirects these
|
|
53
|
+
commands. Integrity scanning happens only in this command.
|
|
54
|
+
|
|
55
|
+
## oats operation run
|
|
56
|
+
|
|
57
|
+
`oats operation run <layer>:<name> (--home <abs> | --soul <name> [--dir
|
|
58
|
+
<scope>] [--agents-root <abs>]) [--arg k=v ...] --json` resolves the
|
|
59
|
+
provider that fills `<layer>` for the home (captured bindings and settings)
|
|
60
|
+
or for the soul in the scope (config), requires the operation to be declared
|
|
61
|
+
(`E_OPERATION_UNKNOWN`), a provider to exist (`E_OPERATION_UNAVAILABLE`,
|
|
62
|
+
also for a home-context operation without `--home`), the executable surface
|
|
63
|
+
to be trusted (`E_CAPABILITY_BLOCKED`) and its `requires` to be on PATH
|
|
64
|
+
(`E_CAPABILITY_REQUIRES`), then runs the provider's own command exactly as
|
|
65
|
+
`oats <ns> <cmd>` would, with cwd and identity set to the selected target
|
|
66
|
+
(`OATS_HOME`, `OATS_INSTANCE`, `OATS_AGENT`, `OATS_SOUL`, `OATS_ROOT`,
|
|
67
|
+
`OATS_CONTEXT`, `OATS_WORKSPACE`, `OATS_OPERATION`, `OATS_SETTINGS`,
|
|
68
|
+
`OATS_CLI_BIN`, team variables) and every ambient identity of the invoking
|
|
69
|
+
process removed. The answer is `{ operation, capability, version, argv,
|
|
70
|
+
cwd, target: {home, instance}|null, result, instance?, home? }`; top-level
|
|
71
|
+
`instance`/`home` appear only when the provider's answer names something it
|
|
72
|
+
launched, never the source home.
|
|
73
|
+
|
|
74
|
+
The receipt must be exactly one JSON-v1 envelope on stdout from a process
|
|
75
|
+
that exits 0. Otherwise the outcome is unconfirmed: `E_OPERATION_TIMEOUT`
|
|
76
|
+
(240 s, below every wrapper) or `E_OPERATION_RESULT` (no valid envelope,
|
|
77
|
+
contaminated output, or a success envelope contradicted by the exit
|
|
78
|
+
status), with `error.details {unconfirmed: true, exit, envelope?, stderr?}`
|
|
79
|
+
carrying what was observed. A provider's own `ok: false` is relayed with
|
|
80
|
+
its code.
|
|
81
|
+
|
|
82
|
+
## Schedules
|
|
83
|
+
|
|
84
|
+
Kind `operation` `{operation: "<layer>:<name>", home}` runs `oats operation
|
|
85
|
+
run <op> --home <home>` as a command job: same admission, tracking and
|
|
86
|
+
reconciliation. An unconfirmed outcome keeps the slot as unknown and any
|
|
87
|
+
name the provider answered is kept for `oats schedule reconcile`.
|
|
88
|
+
|
|
89
|
+
## Mutations
|
|
90
|
+
|
|
91
|
+
`oats use ... --json` answers `{ capability, action: enable|disable|
|
|
92
|
+
layer-none|inherit, target, layer, level, file, settings, before, after
|
|
93
|
+
{..., effective}, remaining?, note?, missingRequires }`. `--inherit`
|
|
94
|
+
removes only the addressed target at this level; other bindings stay and
|
|
95
|
+
are listed. `use none --layer l` is a level statement and takes no soul or
|
|
96
|
+
type. A layer bound to another capability at a level is never overwritten
|
|
97
|
+
(`E_LAYER_BOUND` with the exact remedy).
|
|
98
|
+
|
|
99
|
+
`oats soul set <name> [--dir] [--agents-root] [--runtime] [--model |
|
|
100
|
+
--no-model] [--yolo | --no-yolo] [--backend] [--description |
|
|
101
|
+
--no-description] [--instructions-file <path>] --json` edits only the given
|
|
102
|
+
`soul.yaml` lines and replaces `AGENTS.md`; packaged souls are refused
|
|
103
|
+
(`E_SOUL_READONLY`). The receipt carries before/after and sha256s.
|
|
104
|
+
|
|
105
|
+
## Remote
|
|
106
|
+
|
|
107
|
+
`inspect`, `operation`, `use` and `soul` route with `--server <id>` through
|
|
108
|
+
the saved route. The gate is the destination's `features` list containing
|
|
109
|
+
`operations` and its `operationsApi: 1` (`E_REMOTE_INCOMPATIBLE` before
|
|
110
|
+
anything is sent): `features` describes what a kernel can do locally, which
|
|
111
|
+
is what runs on the host. The probe's `remote` list describes what a CLI
|
|
112
|
+
can ROUTE to a server and is what a GUI checks on the local CLI before
|
|
113
|
+
offering remote actions; it is not a gate on the destination. An explicit `--dir` is the exact member context and
|
|
114
|
+
travels as is; `--home` is its own context; otherwise the registered
|
|
115
|
+
workspace is the scope. Soul instructions travel as bytes on the ssh stdin
|
|
116
|
+
(`--instructions-stdin` on the host), never as a local path.
|
|
117
|
+
|
|
118
|
+
## Replaceability
|
|
119
|
+
|
|
120
|
+
`test/inspect.test.mjs`, `test/operation.test.mjs` and
|
|
121
|
+
`test/operations-routing.test.mjs` use an owned alternative knowledge
|
|
122
|
+
provider (namespace `notes`, one `MEMORY.md`, operations `harvest` and
|
|
123
|
+
`inspect`) and never mention the official provider.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# OATS v0.22.15
|
|
2
|
+
|
|
3
|
+
Souls and capabilities become something you inspect and manage, through
|
|
4
|
+
one kernel contract, and knowledge providers declare what they can do.
|
|
5
|
+
|
|
6
|
+
## Provider operations and inspection (kernel)
|
|
7
|
+
|
|
8
|
+
`oats inspect --json` answers everything a GUI needs in one envelope for a
|
|
9
|
+
scope, a selected soul or a running home: souls with runtime defaults,
|
|
10
|
+
editability and instructions; installed capabilities with health from the
|
|
11
|
+
package engine, separately from their effective activation, provenance and
|
|
12
|
+
settings; effective layer bindings; and the operations each provider
|
|
13
|
+
declares, with availability and the reason when unavailable. For a home the
|
|
14
|
+
answer is its captured bindings (with current manifests and trust), the live
|
|
15
|
+
config beside it, and the drift between them. Same-named souls in member
|
|
16
|
+
repositories are addressed by name plus agents root; a home selects its own
|
|
17
|
+
soul under its own root.
|
|
18
|
+
|
|
19
|
+
`oats operation run <layer>:<name>` resolves the provider that fills a layer
|
|
20
|
+
for a home or a soul, checks declaration, trust and host requirements, runs
|
|
21
|
+
the provider's own command in the right place with the right identity, and
|
|
22
|
+
relays its receipt; a view answers labeled documents. Unconfirmed outcomes
|
|
23
|
+
carry what was observed and a scheduled operation keeps its slot until
|
|
24
|
+
reconciled. Manifests declare `operations`. Schedules gain kind
|
|
25
|
+
`operation`. `oats use --json` answers receipts and `--inherit` returns a
|
|
26
|
+
level to inheritance; `oats soul set` edits an editable soul's defaults and
|
|
27
|
+
instructions in place. All four route to a registered server through the
|
|
28
|
+
saved route; the probe advertises `operations` and `operationsApi: 1`. The
|
|
29
|
+
retire receipt no longer implies a harvest. See
|
|
30
|
+
docs/design/operations-contract.md.
|
|
31
|
+
|
|
32
|
+
## oats.okf 1.6.0
|
|
33
|
+
|
|
34
|
+
The official knowledge provider declares `inspect` (a view of STATE.md,
|
|
35
|
+
log.md and pending notes) and `harvest` (the existing action); the catalog
|
|
36
|
+
pins v1.6.0. Existing scopes keep their locked 1.5.2 until their owners
|
|
37
|
+
update them; the GUI then shows no operations for those providers, which is
|
|
38
|
+
the truthful state.
|
|
39
|
+
|
|
40
|
+
## Desktop
|
|
41
|
+
|
|
42
|
+
Souls & capabilities replaces the launch-centric roster: select a soul to
|
|
43
|
+
inspect it, Launch and Schedule are explicit, defaults and instructions are
|
|
44
|
+
editable where the kernel says so, installed capabilities are shown apart
|
|
45
|
+
from activation with enable, disable and inherit at an exact scope, an
|
|
46
|
+
instance's menu opens Knowledge & capabilities, provider views render as
|
|
47
|
+
text, and schedules run declared provider operations. See
|
|
48
|
+
docs/design/2026-09-07-desktop-souls-capabilities.md.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# OATS v0.22.16
|
|
2
|
+
|
|
3
|
+
The same reviewed content as the unpublished v0.22.15 tag, whose hosted
|
|
4
|
+
run failed in frozen test data (retirement goldens still carrying the
|
|
5
|
+
removed `harvested` field, and a Desktop harvest fixture predating the
|
|
6
|
+
operations gate) before anything was published; only tests changed.
|
|
7
|
+
|
|
8
|
+
Souls and capabilities become something you inspect and manage, through
|
|
9
|
+
one kernel contract, and knowledge providers declare what they can do.
|
|
10
|
+
|
|
11
|
+
## Provider operations and inspection (kernel)
|
|
12
|
+
|
|
13
|
+
`oats inspect --json` answers everything a GUI needs in one envelope for a
|
|
14
|
+
scope, a selected soul or a running home: souls with runtime defaults,
|
|
15
|
+
editability and instructions; installed capabilities with health from the
|
|
16
|
+
package engine, separately from their effective activation, provenance and
|
|
17
|
+
settings; effective layer bindings; and the operations each provider
|
|
18
|
+
declares, with availability and the reason when unavailable. For a home the
|
|
19
|
+
answer is its captured bindings (with current manifests and trust), the live
|
|
20
|
+
config beside it, and the drift between them. Same-named souls in member
|
|
21
|
+
repositories are addressed by name plus agents root; a home selects its own
|
|
22
|
+
soul under its own root.
|
|
23
|
+
|
|
24
|
+
`oats operation run <layer>:<name>` resolves the provider that fills a layer
|
|
25
|
+
for a home or a soul, checks declaration, trust and host requirements, runs
|
|
26
|
+
the provider's own command in the right place with the right identity, and
|
|
27
|
+
relays its receipt; a view answers labeled documents. Unconfirmed outcomes
|
|
28
|
+
carry what was observed and a scheduled operation keeps its slot until
|
|
29
|
+
reconciled. Manifests declare `operations`. Schedules gain kind
|
|
30
|
+
`operation`. `oats use --json` answers receipts and `--inherit` returns a
|
|
31
|
+
level to inheritance; `oats soul set` edits an editable soul's defaults and
|
|
32
|
+
instructions in place. All four route to a registered server through the
|
|
33
|
+
saved route; the probe advertises `operations` and `operationsApi: 1`. The
|
|
34
|
+
retire receipt no longer implies a harvest. See
|
|
35
|
+
docs/design/operations-contract.md.
|
|
36
|
+
|
|
37
|
+
## oats.okf 1.6.0
|
|
38
|
+
|
|
39
|
+
The official knowledge provider declares `inspect` (a view of STATE.md,
|
|
40
|
+
log.md and pending notes) and `harvest` (the existing action); the catalog
|
|
41
|
+
pins v1.6.0. Existing scopes keep their locked 1.5.2 until their owners
|
|
42
|
+
update them; the GUI then shows no operations for those providers, which is
|
|
43
|
+
the truthful state.
|
|
44
|
+
|
|
45
|
+
## Desktop
|
|
46
|
+
|
|
47
|
+
Souls & capabilities replaces the launch-centric roster: select a soul to
|
|
48
|
+
inspect it, Launch and Schedule are explicit, defaults and instructions are
|
|
49
|
+
editable where the kernel says so, installed capabilities are shown apart
|
|
50
|
+
from activation with enable, disable and inherit at an exact scope, an
|
|
51
|
+
instance's menu opens Knowledge & capabilities, provider views render as
|
|
52
|
+
text, and schedules run declared provider operations. See
|
|
53
|
+
docs/design/2026-09-07-desktop-souls-capabilities.md.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# OATS v0.22.17
|
|
2
|
+
|
|
3
|
+
Two corrections found by installed acceptance of 0.22.16, nothing else.
|
|
4
|
+
|
|
5
|
+
## oats.okf 1.6.1
|
|
6
|
+
|
|
7
|
+
A provider view larger than one pipe buffer arrived cut at 65536 bytes
|
|
8
|
+
with exit 0 when the kernel's operation runner or a Desktop read it: the
|
|
9
|
+
provider wrote its envelope and exited at once, and on macOS Node writes
|
|
10
|
+
to a pipe asynchronously. Every answer now leaves the provider whole
|
|
11
|
+
before it exits. The catalog pins v1.6.1; scopes on 1.6.0 keep working
|
|
12
|
+
for small views until their owners run
|
|
13
|
+
`oats update oats.okf --to v1.6.1 --dir <scope>` and re-trust. The
|
|
14
|
+
kernel's runner is unchanged; the regression covers a 128 KiB view
|
|
15
|
+
through an actual pipe, directly and through `oats operation run`, and a
|
|
16
|
+
300 KiB view from any provider.
|
|
17
|
+
|
|
18
|
+
## Desktop
|
|
19
|
+
|
|
20
|
+
Workspaces added at runtime survive an app restart: the opened set is
|
|
21
|
+
persisted on its own (not the recent-suggestion store), restored and
|
|
22
|
+
re-validated at startup with absent paths skipped and aliases collapsed,
|
|
23
|
+
and a backend is reused only when it covers every restored workspace. A
|
|
24
|
+
failed persistence write keeps the previous set and reports the failed
|
|
25
|
+
add.
|
package/docs/schedules.md
CHANGED
|
@@ -56,6 +56,15 @@ and no queue.
|
|
|
56
56
|
home is started again only at due minutes, never every minute, so a
|
|
57
57
|
harness that keeps exiting is not restarted in a loop. A job holds at most
|
|
58
58
|
one pending delivery: a due minute while one is pending adds nothing.
|
|
59
|
+
- **operation** `{id, enabled, cron, tz, kind: "operation", operation, home}`
|
|
60
|
+
— runs a provider operation such as `knowledge:harvest` in the instance at
|
|
61
|
+
`home` through `oats operation run <layer>:<name> --home <home>`. The
|
|
62
|
+
provider is whatever fills that layer for the home when the job runs (its
|
|
63
|
+
snapshot), not something stored in the job, so the job stays valid across
|
|
64
|
+
provider changes and a GUI can list and edit it without parsing argv.
|
|
65
|
+
Admission, tracking and reconciliation are those of a command job: a
|
|
66
|
+
launch receipt the provider answers (a harvester it spawned) is followed
|
|
67
|
+
until that home is gone; the source home is never treated as a launch.
|
|
59
68
|
Unobservable or still starting: skipped with the reason, delivery kept
|
|
60
69
|
pending. Whether a running harness is busy cannot be seen from the
|
|
61
70
|
terminal: delivery is terminal input (bracketed paste plus Enter), never an
|
package/lib/core.mjs
CHANGED
|
@@ -987,9 +987,45 @@ function loadManifestAt(idir, origin) {
|
|
|
987
987
|
if (hookDeclaration(value)?.required && hook !== "spawn") throw new Error(`capability ${id} hook "${hook}" cannot be required — only the spawn hook is enforced (retire and soul-scaffold run outside a spawn transaction)`);
|
|
988
988
|
}
|
|
989
989
|
if (m.agents !== undefined && (!Array.isArray(m.agents) || m.agents.some((a) => typeof a !== "string"))) throw new Error(`capability ${id} "agents" must be an array of package-relative soul directories`);
|
|
990
|
+
validateManifestOperations(m, id);
|
|
990
991
|
return { ...m, _dir: idir, _origin: origin };
|
|
991
992
|
}
|
|
992
993
|
|
|
994
|
+
/** `operations`: what a capability offers a GUI or scheduler by name, each
|
|
995
|
+
* delegating to one of its own `commands`. kind "action" runs something;
|
|
996
|
+
* kind "view" answers { documents: [...] } for presentation (a knowledge
|
|
997
|
+
* provider's own view of an instance's working knowledge). context "home"
|
|
998
|
+
* runs in an instance home; "scope" runs in the config scope. */
|
|
999
|
+
const OPERATION_NAME_RE = /^[a-z][a-z0-9-]*$/;
|
|
1000
|
+
function validateManifestOperations(m, id) {
|
|
1001
|
+
if (m.operations === undefined) return;
|
|
1002
|
+
if (!m.operations || typeof m.operations !== "object" || Array.isArray(m.operations)) throw new Error(`capability ${id} "operations" must be an object map`);
|
|
1003
|
+
for (const [name, op] of Object.entries(m.operations)) {
|
|
1004
|
+
if (!OPERATION_NAME_RE.test(name)) throw new Error(`capability ${id} operation "${name}": name must match ${OPERATION_NAME_RE.source}`);
|
|
1005
|
+
if (!op || typeof op !== "object" || Array.isArray(op)) throw new Error(`capability ${id} operation "${name}" must be an object`);
|
|
1006
|
+
if (typeof op.command !== "string" || !Object.prototype.hasOwnProperty.call(m.commands || {}, op.command)) throw new Error(`capability ${id} operation "${name}": "command" must name one of the manifest's commands`);
|
|
1007
|
+
if (op.kind !== undefined && !["action", "view"].includes(op.kind)) throw new Error(`capability ${id} operation "${name}": "kind" must be action or view`);
|
|
1008
|
+
if (op.context !== undefined && !["home", "scope"].includes(op.context)) throw new Error(`capability ${id} operation "${name}": "context" must be home or scope`);
|
|
1009
|
+
if (op.description !== undefined && typeof op.description !== "string") throw new Error(`capability ${id} operation "${name}": "description" must be a string`);
|
|
1010
|
+
if (op.args !== undefined) {
|
|
1011
|
+
if (!Array.isArray(op.args)) throw new Error(`capability ${id} operation "${name}": "args" must be an array`);
|
|
1012
|
+
for (const a of op.args) {
|
|
1013
|
+
if (!a || typeof a !== "object" || typeof a.name !== "string" || !OPERATION_NAME_RE.test(a.name)) throw new Error(`capability ${id} operation "${name}": each arg needs a name matching ${OPERATION_NAME_RE.source}`);
|
|
1014
|
+
if (a.flag !== undefined && (typeof a.flag !== "string" || !/^--[a-z][a-z0-9-]*$/.test(a.flag))) throw new Error(`capability ${id} operation "${name}" arg "${a.name}": "flag" must look like --name`);
|
|
1015
|
+
if (a.required !== undefined && typeof a.required !== "boolean") throw new Error(`capability ${id} operation "${name}" arg "${a.name}": "required" must be a boolean`);
|
|
1016
|
+
}
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
/** The normalized operations a manifest declares (empty when none). */
|
|
1021
|
+
export function manifestOperations(manifest) {
|
|
1022
|
+
return Object.entries(manifest?.operations || {}).map(([name, op]) => ({
|
|
1023
|
+
name, kind: op.kind || "action", command: op.command, context: op.context || "home",
|
|
1024
|
+
description: op.description || null,
|
|
1025
|
+
args: (op.args || []).map((a) => ({ name: a.name, flag: a.flag || `--${a.name}`, required: !!a.required, description: a.description || null })),
|
|
1026
|
+
}));
|
|
1027
|
+
}
|
|
1028
|
+
|
|
993
1029
|
/** Discover capability manifests. Later sources take precedence: outer scopes < inner scopes; installed < owned within one scope. Duplicates inside one source layer are errors. */
|
|
994
1030
|
export function capabilityManifests(startDir) {
|
|
995
1031
|
// Capability-id keyed — never answer for `constructor`/`toString`. The ids
|
|
@@ -7230,7 +7266,6 @@ export function retireInstance(root, name, o = {}) {
|
|
|
7230
7266
|
contextDir: meta.repo, workspaceDir: workspaceOf(root), rootDir: root, resolved, priorMeta: meta.capabilityMeta || {},
|
|
7231
7267
|
});
|
|
7232
7268
|
}
|
|
7233
|
-
const harvested = hookResults?.meta?.["oats.okf"]?.harvested || [];
|
|
7234
7269
|
|
|
7235
7270
|
// ORDINARY retirement must not delete a home whose cleanup did not finish.
|
|
7236
7271
|
// The failures were already collected above; until now they were read ONLY on
|
|
@@ -7439,7 +7474,7 @@ export function retireInstance(root, name, o = {}) {
|
|
|
7439
7474
|
}
|
|
7440
7475
|
|
|
7441
7476
|
|
|
7442
|
-
const result = { retired: name, agent: found.agent.name, workRecovery, workRecoveries: workRecoveries.length > 1 ? workRecoveries : undefined, worktreeRemoved: isWorktree, branchDeleted: !!(o.deleteBranch && meta.branch) || quarantineBranchDeleted, removedDir: !o.keepDir && (!stillIncomplete || forced), rollbackIncomplete: forced ? undefined : stillIncomplete, forcedIncomplete: forced ? stillIncomplete : undefined, retainedHome: stillIncomplete && !forced ? found.home : undefined,
|
|
7477
|
+
const result = { retired: name, agent: found.agent.name, workRecovery, workRecoveries: workRecoveries.length > 1 ? workRecoveries : undefined, worktreeRemoved: isWorktree, branchDeleted: !!(o.deleteBranch && meta.branch) || quarantineBranchDeleted, removedDir: !o.keepDir && (!stillIncomplete || forced), rollbackIncomplete: forced ? undefined : stillIncomplete, forcedIncomplete: forced ? stillIncomplete : undefined, retainedHome: stillIncomplete && !forced ? found.home : undefined, relinked: relinked.length ? relinked : undefined, capabilityMeta: hookResults?.meta, warnings: hookResults?.warnings?.length ? hookResults.warnings : undefined };
|
|
7443
7478
|
if (self) {
|
|
7444
7479
|
// The caller is the instance: its process lives in the window we are about to
|
|
7445
7480
|
// kill. Detach the kill so this function can return and the caller can report
|