@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/lib/resolve.mjs
CHANGED
|
@@ -60,6 +60,7 @@ import * as defaultRemote from "./remote.mjs";
|
|
|
60
60
|
import { BY_TEAM_REMOVED, reportRows, soulKeyOf, soulTeams, teamModel } from "./teams.mjs";
|
|
61
61
|
import { bindRemote, packageProviding, readPackageManifests, validateLock } from "./packages.mjs";
|
|
62
62
|
import { settingValueProblems } from "./capability-contract.mjs";
|
|
63
|
+
import { memberRowByKey } from "./workspace.mjs";
|
|
63
64
|
|
|
64
65
|
export const RESOLUTION_API = 1;
|
|
65
66
|
export const SLOTS = Object.freeze(["knowledge", "messaging", "tasks"]);
|
|
@@ -367,14 +368,26 @@ export function composeCapabilities(workspace, soulDefinition, { offs = null } =
|
|
|
367
368
|
|
|
368
369
|
/* ───────────────────────────── lookups ────────────────────────────────── */
|
|
369
370
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}
|
|
371
|
+
/** A discovery's member row by key (the first with that key; lib/workspace.mjs memberRowByKey). */
|
|
372
|
+
const memberRow = (discovery, repoKey) => memberRowByKey(discovery?.members, repoKey);
|
|
373
373
|
|
|
374
|
-
/** The ref the workspace lists a member under (keeps the operator's spelling: ssh vs https); else from the key.
|
|
374
|
+
/** The ref the workspace lists a member under (keeps the operator's spelling: ssh vs https); else from the key.
|
|
375
|
+
* The key → first listed ref index is built once per members array and parseRepoRef. */
|
|
376
|
+
const memberRefIndex = new WeakMap();
|
|
375
377
|
export function memberRef(discovery, remote, repoKey) {
|
|
376
|
-
|
|
377
|
-
|
|
378
|
+
const members = discovery?.workspace?.members;
|
|
379
|
+
if (Array.isArray(members)) {
|
|
380
|
+
let byParser = memberRefIndex.get(members);
|
|
381
|
+
if (!byParser) memberRefIndex.set(members, byParser = new Map());
|
|
382
|
+
let index = byParser.get(remote.parseRepoRef);
|
|
383
|
+
if (!index) {
|
|
384
|
+
index = new Map();
|
|
385
|
+
for (const ref of members) {
|
|
386
|
+
try { const key = remote.parseRepoRef(ref).key; if (!index.has(key)) index.set(key, ref); } catch { /* validated upstream */ }
|
|
387
|
+
}
|
|
388
|
+
byParser.set(remote.parseRepoRef, index);
|
|
389
|
+
}
|
|
390
|
+
if (index.has(repoKey)) return index.get(repoKey);
|
|
378
391
|
}
|
|
379
392
|
return refForKey(repoKey);
|
|
380
393
|
}
|
|
@@ -496,7 +509,10 @@ async function enumerateSkills({ remote, ref, commit, dir, manifest, listing, mo
|
|
|
496
509
|
// Discovery already listed the capability directory (relative to it): project onto this entry.
|
|
497
510
|
entries = listing.filter((e) => e.path === declared || e.path.startsWith(`${declared}/`)).map((e) => ({ ...e, path: e.path === declared ? "" : e.path.slice(declared.length + 1) })).filter((e) => e.path);
|
|
498
511
|
} else {
|
|
499
|
-
|
|
512
|
+
// A listing at a commit never changes: the parsed cache keeps it (lib/remote.mjs memoAtCommit).
|
|
513
|
+
const at = posix.join(dir, declared);
|
|
514
|
+
const list = () => remote.listRemoteTree(ref, commit, at, { depth: 2 });
|
|
515
|
+
entries = typeof remote.memoAtCommit === "function" ? await remote.memoAtCommit(ref, commit, `list\0${at}\0${2}`, list) : await list();
|
|
500
516
|
}
|
|
501
517
|
const found = skillsInListing(declared, entries);
|
|
502
518
|
if (found.length === 0) throw missing(raw, "skill-missing", `declares skills entry ${show(raw)} but no SKILL.md is there (neither ${declared}/SKILL.md nor ${declared}/*/SKILL.md)`);
|
package/lib/schedule.mjs
CHANGED
|
@@ -29,6 +29,7 @@ import { fileURLToPath } from "node:url";
|
|
|
29
29
|
import { Cron } from "croner";
|
|
30
30
|
import { loadLocal } from "./workspace.mjs";
|
|
31
31
|
import { noteRuntimeName } from "./deprecation.mjs";
|
|
32
|
+
import { herdrSettingRemoved } from "./errors.mjs";
|
|
32
33
|
import { tickTriggers } from "./triggers.mjs";
|
|
33
34
|
import { resolveMemberClone } from "./instance-resolution.mjs";
|
|
34
35
|
import { AUTOMATION_ID_RE, MODEL_RE, automationContext, automationError, baseRow, localEntry, localId, soulOriginOf, splitId } from "./automations.mjs";
|
|
@@ -38,7 +39,6 @@ export const SCHEDULE_FILE = "oats-schedules.json";
|
|
|
38
39
|
export const SCHEDULE_API = 2;
|
|
39
40
|
const ID_RE = /^[a-z0-9-]{1,40}$/;
|
|
40
41
|
const HARNESSES = new Set(["pi", "claude", "codex"]);
|
|
41
|
-
const BACKENDS = new Set(["tmux", "herdr"]);
|
|
42
42
|
const MESSAGE_MAX = 256 * 1024;
|
|
43
43
|
const OATS_BIN = join(dirname(fileURLToPath(import.meta.url)), "..", "bin", "oats.mjs");
|
|
44
44
|
const COMMAND_TIMEOUT_MS = 5 * 60 * 1000;
|
|
@@ -295,8 +295,17 @@ function validateMessage(message, field) {
|
|
|
295
295
|
if (typeof message !== "string" || !message.trim() || message.includes("\0") || Buffer.byteLength(message) > MESSAGE_MAX) throw scheduleError("E_SCHEDULE_INVALID", `${field}: non-empty text without NUL, at most 256 KiB`, { field });
|
|
296
296
|
}
|
|
297
297
|
|
|
298
|
-
/**
|
|
299
|
-
|
|
298
|
+
/** The refusal of a spawn job's `backend: herdr` (Herdr was removed in 0.31.0): `where` names the
|
|
299
|
+
* file and key that set it. */
|
|
300
|
+
export function herdrBackendRefusal(where) { return Object.assign(herdrSettingRemoved(`${where}: herdr`), { field: "backend" }); }
|
|
301
|
+
/** A stored local spawn job that selects Herdr: its refusal, naming this scope's definitions file. */
|
|
302
|
+
function storedHerdrBackend(ws, id, def) {
|
|
303
|
+
return def?.kind === "spawn" && !def.workspace && def.backend === "herdr" ? herdrBackendRefusal(`${definitionsPath(ws)} sets jobs.${id}.backend`) : null;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/** Validate and normalize one definition against its scope. `backendSource` names the file and key
|
|
307
|
+
* a definition read from elsewhere sets its backend in (default: this scope's definitions file). */
|
|
308
|
+
export function validateDefinition(ws, def, { checkAgent = true, backendSource } = {}) {
|
|
300
309
|
if (!def || typeof def !== "object" || Array.isArray(def)) throw scheduleError("E_SCHEDULE_INVALID", "definition must be an object", { field: "definition" });
|
|
301
310
|
if (def.kind === "trigger") throw scheduleError("E_SCHEDULE_INVALID", "kind: a trigger is added with `oats trigger add`, not `oats schedule add`", { field: "kind" });
|
|
302
311
|
const id = def.id;
|
|
@@ -314,7 +323,8 @@ export function validateDefinition(ws, def, { checkAgent = true } = {}) {
|
|
|
314
323
|
out.agentsRoot = resolve(def.agentsRoot);
|
|
315
324
|
}
|
|
316
325
|
if (def.repo !== undefined) { if (typeof def.repo !== "string" || !def.repo.trim() || def.repo.startsWith("-")) throw scheduleError("E_SCHEDULE_INVALID", "repo: the work repository path, as oats spawn --repo", { field: "repo" }); out.repo = def.repo; }
|
|
317
|
-
if (def.backend
|
|
326
|
+
if (def.backend === "herdr") throw herdrBackendRefusal(backendSource ?? `${definitionsPath(ws)} sets jobs.${id}.backend`);
|
|
327
|
+
if (def.backend !== undefined) { if (def.backend !== "tmux") throw scheduleError("E_SCHEDULE_INVALID", "backend: tmux", { field: "backend" }); out.backend = def.backend; }
|
|
318
328
|
if (def.purpose !== undefined) { if (typeof def.purpose !== "string" || !/^[a-z0-9-]{1,40}$/.test(def.purpose)) throw scheduleError("E_SCHEDULE_INVALID", "purpose: lowercase letters, digits and dashes", { field: "purpose" }); out.purpose = def.purpose; }
|
|
319
329
|
// A run spawns <agent>-<purpose|id>-<YYYYMMDDHHMM>; instance names are at most
|
|
320
330
|
// MAX_INSTANCE_NAME characters, so refuse here what would fail at every firing.
|
|
@@ -413,7 +423,7 @@ export function validateWorkspaceSchedule(dep, a) {
|
|
|
413
423
|
const real = (p) => { try { return realpathSync(p); } catch { return resolve(p); } };
|
|
414
424
|
if (!inside(real(dep), real(spec.cwd))) throw scheduleError("E_SCHEDULE_INVALID", `cwd: ${JSON.stringify(d.cwd)} leaves the deployment; a workspace command's cwd is a directory inside it`, { field: "cwd" });
|
|
415
425
|
}
|
|
416
|
-
const out = validateDefinition(dep, spec, { checkAgent: false });
|
|
426
|
+
const out = validateDefinition(dep, spec, { checkAgent: false, backendSource: `${a.origin?.path ?? a.name} sets backend` });
|
|
417
427
|
delete out.enabled;
|
|
418
428
|
return out;
|
|
419
429
|
}
|
|
@@ -756,11 +766,13 @@ export function tickWorkspace(ws, { now = new Date(), io, reg, wsList, dryRun =
|
|
|
756
766
|
const def = defs.jobs[id];
|
|
757
767
|
const js = stateOf(id);
|
|
758
768
|
let due, cronError;
|
|
759
|
-
const
|
|
769
|
+
const invalidShape = shapeError(def), herdr = invalidShape ? null : storedHerdrBackend(ws, id, def);
|
|
770
|
+
const shape = invalidShape ?? herdr?.message;
|
|
760
771
|
if (!shape) validateActiveExecution(js, jobLockInfo(ws, id)); // Also covers host pre-observed candidates.
|
|
761
772
|
if (shape) { cronError = shape; due = false; }
|
|
762
773
|
else { try { due = only ? true : dueAt(def, minute); } catch (e) { cronError = e.message; due = false; } }
|
|
763
|
-
|
|
774
|
+
const errorCode = herdr ? { errorCode: herdr.code } : {};
|
|
775
|
+
if (cronError) { if (!dryRun) js.lastRun = { ...(js.lastRun || {}), outcome: "invalid", error: `definition cannot be evaluated: ${cronError}`, ...errorCode }; record(id, "invalid", { error: cronError, ...errorCode }); continue; }
|
|
764
776
|
if (def.kind === "wake" && js.pendingWake && def.enabled && !dryRun && !only) {
|
|
765
777
|
const dueNow = due && js.lastAttemptedMinute !== key;
|
|
766
778
|
const held = !!jobLockInfo(ws, id);
|
|
@@ -1066,7 +1078,8 @@ export function describe(ws, qid, io, { defs, st, now = new Date(), ctx = null }
|
|
|
1066
1078
|
const { history, recentRuns } = projectHistory(js);
|
|
1067
1079
|
const { workspace: _w, ...stored } = def; void _w;
|
|
1068
1080
|
const base = { ...stored, id, scope: ws, scheduleApi: 2, scheduleHistoryApi: SCHEDULE_HISTORY_API, executionStatus: scheduleExecutionStatus(def, intent, lock), nextRun, lastRun: js.lastRun ? { ...js.lastRun, runId: runIdOf(js.lastRun), session: sessionProvenanceOf(js.lastRun) } : null, history, recentRuns, running: !!lock, ...(js.attempt ? { attempt: js.attempt } : {}), ...(js.pendingWake ? { pendingWake: js.pendingWake } : {}) };
|
|
1069
|
-
|
|
1081
|
+
const herdr = entry ? null : storedHerdrBackend(ws, id, def);
|
|
1082
|
+
return scheduleRow(ws, entry ?? localEntry("schedule", { ...def, id: name }, { dep: ws, ...(herdr ? { invalid: { code: herdr.code, message: herdr.message, field: herdr.field } } : {}) }), base, ctx);
|
|
1070
1083
|
}
|
|
1071
1084
|
/** A schedule's list row: its stored facts, the fields every trigger and schedule row share
|
|
1072
1085
|
* (lib/automations.mjs baseRow) and the schedule's own: `kind` is its run (spawn, command, wake,
|