@awebai/oats 0.29.3 → 0.30.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/README.md +12 -6
- package/bin/oats.mjs +203 -54
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +538 -204
- package/capabilities/oats-aweb/injects/aweb.md +1 -1
- package/capabilities/oats-aweb/lib/binding-wire.mjs +31 -22
- package/capabilities/oats-aweb/oats.json +5 -12
- package/capabilities/oats-aweb/skills/VENDORED.md +4 -4
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +1 -1
- package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +83 -13
- package/capabilities/oats-code-review/injects/reviewer.md +26 -0
- package/capabilities/oats-code-review/oats.json +16 -0
- package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +66 -0
- package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +30 -0
- package/capabilities/oats-code-review/skills/security-review/SKILL.md +56 -0
- package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +34 -0
- package/capabilities/oats-developer/injects/developer.md +38 -0
- package/capabilities/oats-developer/oats.json +17 -0
- package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +43 -0
- package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +47 -0
- package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +65 -0
- package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +37 -0
- package/capabilities/oats-developer/skills/worktrees/SKILL.md +36 -0
- package/capabilities/oats-engineering-expert/injects/expert.md +37 -0
- package/capabilities/oats-engineering-expert/oats.json +17 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +37 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +52 -0
- package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +50 -0
- package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +53 -0
- package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +49 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +16 -9
- package/capabilities/oats-okf/lib/binding-wire.mjs +47 -15
- package/capabilities/oats-okf/lib/config.mjs +2 -1
- package/capabilities/oats-okf/lib/consult.mjs +26 -4
- package/capabilities/oats-okf/lib/harvest-switch.mjs +16 -3
- package/capabilities/oats-okf/lib/inspection.mjs +26 -7
- package/capabilities/oats-okf/lib/io.mjs +9 -1
- package/capabilities/oats-okf/lib/sources.mjs +34 -3
- package/capabilities/oats-okf/lib/stores.mjs +8 -6
- package/capabilities/oats-okf/lib/worker.mjs +7 -17
- package/capabilities/oats-okf/oats.json +6 -3
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +2 -2
- package/capabilities/oats-okf-harvest/oats.json +3 -3
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +6 -6
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +37 -16
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +1 -1
- package/capabilities/oats-okf-maintenance/lib/provenance.mjs +6 -1
- package/capabilities/oats-okf-maintenance/oats.json +2 -2
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +17 -2
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +11 -23
- package/capabilities/oats-workspace-experts/injects/oats-experts.md +26 -0
- package/capabilities/oats-workspace-experts/oats.json +9 -0
- package/docs/capabilities.md +160 -171
- package/docs/capability-manifest.schema.json +7 -10
- package/docs/configuration.md +213 -64
- package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
- package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
- package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
- package/docs/design/2026-09-27-team-model-v2.md +116 -0
- package/docs/design/2026-09-28-automations-trust.md +38 -0
- package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
- package/docs/design/HISTORY.md +65 -0
- package/docs/design/README.md +23 -54
- package/docs/desktop-cli-api.md +1787 -1777
- package/docs/desktop.md +30 -91
- package/docs/execution-targets.md +146 -292
- package/docs/first-team.md +31 -17
- package/docs/implementation.md +76 -288
- package/docs/integrations.md +118 -320
- package/docs/knowledge-capability-authoring.md +25 -52
- package/docs/knowledge-reference/acceptance.md +3 -3
- package/docs/knowledge-reference/adoption.md +1 -1
- package/docs/knowledge-reference/harvester.md +2 -2
- package/docs/knowledge-reference/package-craft.md +3 -3
- package/docs/knowledge-reference/provider-mapping.md +3 -6
- package/docs/knowledge-reference/reader-capture.md +3 -3
- package/docs/knowledge-theory.md +62 -166
- package/docs/knowledge.md +225 -404
- package/docs/layers.md +42 -97
- package/docs/oats-local.schema.json +58 -5
- package/docs/oats-membership.schema.json +1 -8
- package/docs/oats-package.schema.json +5 -5
- package/docs/oats-workspace.schema.json +8 -22
- package/docs/official-catalog.md +25 -28
- package/docs/packages.md +45 -63
- package/docs/plans/0.30-close-out.md +61 -0
- package/docs/release-lane.md +77 -0
- package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
- package/docs/release-notes/v0.19.0.md +48 -147
- package/docs/release-notes/v0.19.1.md +2 -3
- package/docs/release-notes/v0.19.3.md +2 -15
- package/docs/release-notes/v0.20.0.md +0 -15
- package/docs/release-notes/v0.22.0.md +71 -138
- package/docs/release-notes/v0.22.1.md +42 -90
- package/docs/release-notes/v0.22.10.md +1 -1
- package/docs/release-notes/v0.22.11.md +1 -47
- package/docs/release-notes/v0.22.12.md +4 -13
- package/docs/release-notes/v0.22.13.md +1 -42
- package/docs/release-notes/v0.22.14.md +3 -11
- package/docs/release-notes/v0.22.15.md +1 -46
- package/docs/release-notes/v0.22.16.md +6 -8
- package/docs/release-notes/v0.22.18.md +1 -99
- package/docs/release-notes/v0.22.19.md +3 -14
- package/docs/release-notes/v0.22.2.md +6 -15
- package/docs/release-notes/v0.22.3.md +0 -1
- package/docs/release-notes/v0.22.4.md +1 -14
- package/docs/release-notes/v0.22.5.md +2 -12
- package/docs/release-notes/v0.22.6.md +0 -3
- package/docs/release-notes/v0.23.0.md +9 -25
- package/docs/release-notes/v0.23.1.md +9 -25
- package/docs/release-notes/v0.23.2.md +2 -4
- package/docs/release-notes/v0.24.0.md +56 -97
- package/docs/release-notes/v0.24.1.md +7 -11
- package/docs/release-notes/v0.24.10.md +34 -45
- package/docs/release-notes/v0.24.11.md +12 -20
- package/docs/release-notes/v0.24.12.md +35 -48
- package/docs/release-notes/v0.24.13.md +34 -41
- package/docs/release-notes/v0.24.2.md +9 -13
- package/docs/release-notes/v0.24.3.md +7 -11
- package/docs/release-notes/v0.24.4.md +6 -6
- package/docs/release-notes/v0.24.5.md +6 -10
- package/docs/release-notes/v0.24.6.md +2 -5
- package/docs/release-notes/v0.24.7.md +46 -75
- package/docs/release-notes/v0.24.8.md +58 -96
- package/docs/release-notes/v0.24.9.md +38 -54
- package/docs/release-notes/v0.25.0.md +59 -76
- package/docs/release-notes/v0.25.1.md +57 -81
- package/docs/release-notes/v0.25.2.md +51 -70
- package/docs/release-notes/v0.25.3.md +11 -13
- package/docs/release-notes/v0.25.4.md +9 -13
- package/docs/release-notes/v0.25.5.md +3 -5
- package/docs/release-notes/v0.25.6.md +20 -29
- package/docs/release-notes/v0.25.7.md +5 -7
- package/docs/release-notes/v0.25.8.md +26 -39
- package/docs/release-notes/v0.26.0.md +175 -646
- package/docs/release-notes/v0.27.0.md +4 -5
- package/docs/release-notes/v0.27.1.md +4 -6
- package/docs/release-notes/v0.27.2.md +1 -1
- package/docs/release-notes/v0.28.0.md +57 -124
- package/docs/release-notes/v0.29.0.md +89 -208
- package/docs/release-notes/v0.29.1.md +1 -1
- package/docs/release-notes/v0.29.2.md +3 -4
- package/docs/release-notes/v0.29.4.md +90 -0
- package/docs/release-notes/v0.30.0.md +205 -0
- package/docs/schedules.md +280 -349
- package/docs/servers.md +99 -117
- package/docs/soul.schema.json +2 -9
- package/docs/souls-and-instances.md +145 -158
- package/docs/workspaces.md +132 -215
- package/lib/automations.mjs +28 -6
- package/lib/core.mjs +226 -74
- package/lib/instance-events.mjs +1 -1
- package/lib/instance-inspect.mjs +109 -34
- package/lib/instance-lifecycle.mjs +14 -1
- package/lib/instance-resolution.mjs +26 -27
- package/lib/launch-preference.mjs +87 -0
- package/lib/materialize.mjs +3 -3
- package/lib/packages.mjs +2 -5
- package/lib/resolve.mjs +29 -87
- package/lib/schedule.mjs +32 -18
- package/lib/teams-verbs.mjs +195 -0
- package/lib/teams.mjs +190 -0
- package/lib/triggers.mjs +53 -17
- package/lib/workspace.mjs +54 -147
- package/package-catalog.json +9 -15
- package/package.json +1 -1
- package/skills/oats-getting-started/SKILL.md +25 -13
- package/capabilities/oats-review/injects/review.md +0 -69
- package/capabilities/oats-review/oats.json +0 -10
- package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
- package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
- package/docs/conventions.md +0 -90
- package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
- package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
- package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
- package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
- package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
- package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
- package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
- package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
- package/docs/design/2026-09-15-captured-dispatch.md +0 -127
- package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
- package/docs/design/2026-09-15-package-preparation.md +0 -100
- package/docs/design/2026-09-15-portable-data-contract.md +0 -121
- package/docs/design/2026-09-15-portable-declarations.md +0 -189
- package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
- package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
- package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
- package/docs/design/2026-09-15-source-observation.md +0 -119
- package/docs/design/2026-09-16-captured-admission.md +0 -77
- package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
- package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
- package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
- package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
- package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
- package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
- package/docs/design/2026-09-16-portable-onboarding.md +0 -179
- package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
- package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
- package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
- package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
- package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
- package/docs/design/2026-09-17-captured-native-start.md +0 -58
- package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
- package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
- package/docs/design/2026-09-17-public-captured-start.md +0 -108
- package/docs/design/2026-09-17-public-prepare-request.md +0 -90
- package/docs/design/2026-09-18-captured-pi-host.md +0 -205
- package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
- package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
- package/docs/design/2026-09-20-redesign-program-board.md +0 -142
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
- package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
- package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
- package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
- package/docs/design/2026-09-24-phase-d-plan.md +0 -305
- package/docs/design/2026-09-25-teams-contract.md +0 -258
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
- package/docs/design/desktop-ux-plan.md +0 -362
- package/docs/design/launch-configurations.md +0 -168
- package/docs/design/okf-mirror-provenance.md +0 -105
- package/docs/design/operations-contract.md +0 -141
- package/docs/oats-member.schema.json +0 -38
- package/skills/integration-authoring/SKILL.md +0 -84
- package/skills/oats-support/SKILL.md +0 -79
- package/skills/skill-craft/SKILL.md +0 -109
- package/skills/soul-craft/SKILL.md +0 -116
package/bin/oats.mjs
CHANGED
|
@@ -27,12 +27,14 @@ import {
|
|
|
27
27
|
LAYERS, OATS_VERSION, manifestOperations, upgradeHomeMeta,
|
|
28
28
|
capabilityManifests, capabilityTrust, capabilityExecutablePath,
|
|
29
29
|
officialPackageCatalog, officialCatalogFile, officialCapabilityAliases, resolvedFromHome, resolvedFromPrepared, teamEnv, isWorkspaceHome, preWorkspaceHome, isCapturedHome, capturedHomeRefusal, composeInstanceAgentsMd, parseYamlNested, withConfigFile,
|
|
30
|
-
findInstanceHome, findInstanceHomes, workspaceOf, stopInstanceSession, ensureRoot, findRoot, findAgent, findAgentAt, legacyLocalAgents, legacyCapturedHomes, listAgents, listInstances, servedIdentityLine, spawnInstanceAsync, instanceSoulDir, launchConfigsAt, explicitInstanceName, retireInstance, inspectInstanceSession, inputInstanceSession, attachInstanceSession, startInstanceSession, defaultRepo, RELATIONS, validateLaunchConfig, renderLaunchRecipe, describeLaunchCommand, redactLaunchRecipe, LAUNCH_HARNESSES, planLaunch, redactLaunchCommand, restartInstanceSession,
|
|
30
|
+
findInstanceHome, findInstanceHomes, workspaceOf, stopInstanceSession, ensureRoot, findRoot, findAgent, findAgentAt, legacyLocalAgents, legacyCapturedHomes, listAgents, listInstances, servedIdentityLine, spawnInstanceAsync, instanceSoulDir, launchConfigsAt, launchReportFor, explicitInstanceName, retireInstance, inspectInstanceSession, inputInstanceSession, attachInstanceSession, startInstanceSession, defaultRepo, RELATIONS, validateLaunchConfig, renderLaunchRecipe, describeLaunchCommand, redactLaunchRecipe, LAUNCH_HARNESSES, planLaunch, redactLaunchCommand, restartInstanceSession,
|
|
31
31
|
} from "../lib/core.mjs";
|
|
32
32
|
import {
|
|
33
33
|
writeFileAtomic, LOCK_FILE, readLock, writeLock, resolvePackages, memoizedRemote,
|
|
34
34
|
classifyPackageValue, parsePackageRequest } from "../lib/packages.mjs";
|
|
35
35
|
import { loadLocal, validateWorkspace, validateLocal, discoverPackageSouls, workspaceWarnings } from "../lib/workspace.mjs";
|
|
36
|
+
import { recordedTeams, reportRows, soulKeyOf, soulTeams, teamModel } from "../lib/teams.mjs";
|
|
37
|
+
import { launchLayers } from "../lib/launch-preference.mjs";
|
|
36
38
|
import { parseConfigData } from "../lib/config-data.mjs";
|
|
37
39
|
import * as remoteModule from "../lib/remote.mjs";
|
|
38
40
|
import YAML from "yaml";
|
|
@@ -55,7 +57,8 @@ const rawArgs = process.argv.slice(2);
|
|
|
55
57
|
const KERNEL_SWITCHES = new Set(["allow-child-spawns", "apply", "check", "clear", "delete-branch", "discard-worktree", "dry-run", "ephemeral", "force", "help", "host", "json", "keep-dir", "keep-env", "no-child-spawns", "no-launch", "no-recursive", "no-yolo", "plan", "policy", "preview", "print", "replace", "self", "verbose", "yes", "yolo"]);
|
|
56
58
|
/** `--flag=value` is `--flag value`: every kernel reader (flag(), valueFlag(), the onboard and
|
|
57
59
|
* routed-command loops) then applies the spaced form's validation to it. `problem` is an empty
|
|
58
|
-
* `--flag
|
|
60
|
+
* `--flag=`, a switch given a value, or a value that is itself an option (`--model=--yolo`):
|
|
61
|
+
* expanded, it would be a flag token every reader sees, which the spaced form can never carry. */
|
|
59
62
|
function expandInlineValues(argv) {
|
|
60
63
|
const out = [];
|
|
61
64
|
let problem;
|
|
@@ -63,7 +66,7 @@ function expandInlineValues(argv) {
|
|
|
63
66
|
const eq = a.indexOf("=");
|
|
64
67
|
if (!a.startsWith("--") || eq <= 2) { out.push(a); continue; }
|
|
65
68
|
const name = a.slice(2, eq), value = a.slice(eq + 1);
|
|
66
|
-
problem ??= KERNEL_SWITCHES.has(name) ? `--${name} takes no value (got ${a})` : value === "" ? `--${name}= needs a value` : undefined;
|
|
69
|
+
problem ??= KERNEL_SWITCHES.has(name) ? `--${name} takes no value (got ${a})` : value === "" ? `--${name}= needs a value` : value.startsWith("--") ? `--${name}= takes a value, not an option (got ${a})` : undefined;
|
|
67
70
|
out.push(`--${name}`, value);
|
|
68
71
|
}
|
|
69
72
|
return { argv: out, problem };
|
|
@@ -71,7 +74,7 @@ function expandInlineValues(argv) {
|
|
|
71
74
|
const { argv: args, problem: argvProblem } = expandInlineValues(rawArgs);
|
|
72
75
|
let cmd = args[0];
|
|
73
76
|
const HELP_WORDS = new Set(["help", "--help", "-h"]);
|
|
74
|
-
const KERNEL_COMMANDS = new Set(["automations", "trigger", "capture", "capabilities", "doctor", "inspect", "instance", "operation", "package", "readiness", "souls", "launch-config", "experimental", "onboard", "pane", "recall", "retire", "root", "schedule", "server", "session", "setup", "spawn", "status", "sync", "update", "version", "workspace"]);
|
|
77
|
+
const KERNEL_COMMANDS = new Set(["automations", "trigger", "capture", "capabilities", "doctor", "inspect", "instance", "operation", "package", "readiness", "souls", "soul", "teams", "launch-config", "experimental", "onboard", "pane", "recall", "retire", "root", "schedule", "server", "session", "setup", "spawn", "status", "sync", "update", "version", "workspace"]);
|
|
75
78
|
/** Commands whose argv another parser reads (packages/record and packages/experimental parse process.argv). */
|
|
76
79
|
const OWN_ARGV_COMMANDS = new Set(["capture", "recall", "setup", "experimental"]);
|
|
77
80
|
/** Commands `--server <id>` runs on a registered server. */
|
|
@@ -410,18 +413,17 @@ function printWorkspaceInspect(doc) {
|
|
|
410
413
|
for (const p of doc.problems) console.log(` ! ${p.code}: ${p.message}`);
|
|
411
414
|
}
|
|
412
415
|
|
|
413
|
-
/** `{ teams, teamsSource }` for a session start of a workspace home: its
|
|
414
|
-
* live (
|
|
415
|
-
*
|
|
416
|
-
* answer. Anything that is not a readable workspace home gets nothing here — the start
|
|
416
|
+
/** `{ teams, defaultTeam, teamsSource }` for a session start of a workspace home: its teams read
|
|
417
|
+
* live (the workspace host + oats-local.yaml), which the launch hook re-checks joined memberships
|
|
418
|
+
* against — or the spawn record, marked `recorded`, when the read cannot answer. Anything that is not a readable workspace home gets nothing here — the start
|
|
417
419
|
* itself refuses it. */
|
|
418
420
|
async function homeLiveTeams(home) {
|
|
419
421
|
let meta;
|
|
420
422
|
try { meta = JSON.parse(readFileSync(join(home, "instance.json"), "utf8")); } catch { return {}; }
|
|
421
423
|
if (!isWorkspaceHome(meta)) return {};
|
|
422
424
|
const { liveTeams } = await import("../lib/instance-resolution.mjs");
|
|
423
|
-
const { teams, source } = await liveTeams(home, meta, { remoteOptions: remoteOptionsFromEnv() });
|
|
424
|
-
return Array.isArray(teams) ? { teams, teamsSource: source } : {};
|
|
425
|
+
const { teams, defaultTeam, source } = await liveTeams(home, meta, { remoteOptions: remoteOptionsFromEnv() });
|
|
426
|
+
return Array.isArray(teams) ? { teams, defaultTeam, teamsSource: source } : {};
|
|
425
427
|
}
|
|
426
428
|
|
|
427
429
|
// ---------- operation run: generic invoke through the capability engine ----------
|
|
@@ -1071,33 +1073,46 @@ const short = (oid) => (typeof oid === "string" ? oid.slice(0, 8) : "?");
|
|
|
1071
1073
|
/** Display name of a discovery: the workspace's name, or the standalone label (decision 10). */
|
|
1072
1074
|
const workspaceName = (discovery) => discovery.workspace?.name ?? `standalone:${memberLabel(discovery.key)}`;
|
|
1073
1075
|
const memberLabel = (key) => String(key).split("/").filter(Boolean).pop()?.replace(/\.git$/, "") || String(key);
|
|
1074
|
-
const teamLabel = (team) => team ?? "unassigned";
|
|
1075
1076
|
const originOf = (item) => (item.package ? `package ${item.package} v${item.version}` : `member ${item.repoKey} @ ${short(item.commit)}`);
|
|
1076
1077
|
|
|
1077
|
-
/** A soul's
|
|
1078
|
-
*
|
|
1079
|
-
|
|
1078
|
+
/** A soul's launch here (feature launch-preference): the `Launch` report a spawn with no flags would decide
|
|
1079
|
+
* (souls.launch, the soul's own launch, the host default), and the desktop-facts `harness`, `model`,
|
|
1080
|
+
* `harnessFrom` equal to its effective launch (`harnessFrom` "kernel-default" for the host default). */
|
|
1081
|
+
function soulLaunchFacts(entry, local, { launchConfigs, contextDir }) {
|
|
1082
|
+
const launch = launchReportFor({ layers: launchLayers({ definition: entry.definition, entry, key: soulKeyOf(entry), local }), launchConfigs, contextDir });
|
|
1083
|
+
return { harness: launch.effective.harness, model: launch.effective.model, harnessFrom: launch.from === "host" ? "kernel-default" : launch.from, launch };
|
|
1084
|
+
}
|
|
1080
1085
|
|
|
1081
1086
|
/** Rows of every soul and capability of confirmed members (+ external souls) + locked package
|
|
1082
1087
|
* capabilities. Souls have no private mode (0.26.0); a private member capability is listed with
|
|
1083
|
-
* `private: true` — repo-owned: usable only by its own repo's souls (E_CAPABILITY_PRIVATE).
|
|
1084
|
-
|
|
1088
|
+
* `private: true` — repo-owned: usable only by its own repo's souls (E_CAPABILITY_PRIVATE).
|
|
1089
|
+
* Each soul row carries its teams HERE (team model v2: `teams`, `defaultTeam`), from the committed
|
|
1090
|
+
* shared teams and `local` (oats-local.yaml); both null when its teams do not resolve (E_TEAM_*). */
|
|
1091
|
+
function workspaceItems(discovery, lock, local, deploymentDir) {
|
|
1085
1092
|
const souls = [];
|
|
1093
|
+
let launchConfigs = {};
|
|
1094
|
+
try { launchConfigs = deploymentDir ? launchConfigsAt(deploymentDir) : {}; } catch { launchConfigs = {}; }
|
|
1095
|
+
const soulDefaults = (entry) => soulLaunchFacts(entry, local, { launchConfigs, contextDir: deploymentDir });
|
|
1086
1096
|
const capabilities = [];
|
|
1097
|
+
const model = teamModel(discovery.standalone === true ? null : discovery.workspace, local, { workspaceKey: discovery.key ?? null });
|
|
1098
|
+
const teamsHere = (entry) => {
|
|
1099
|
+
try { const t = soulTeams(model, soulKeyOf(entry)); return { teams: reportRows(t.teams), defaultTeam: t.defaultTeam }; }
|
|
1100
|
+
catch (e) { if (String(e?.code).startsWith("E_TEAM_")) return { teams: null, defaultTeam: null }; throw e; }
|
|
1101
|
+
};
|
|
1087
1102
|
for (const m of discovery.members) {
|
|
1088
1103
|
if (!m.confirmed && !(discovery.standalone === true && m.key === discovery.key)) continue;
|
|
1089
|
-
for (const s of m.souls) souls.push({ name: s.name, origin: originOf(s), kind: "member", repoKey: s.repoKey, commit: s.commit,
|
|
1090
|
-
for (const c of m.capabilities) capabilities.push({ name: c.name, origin: originOf(c), kind: "member", repoKey: c.repoKey, commit: c.commit,
|
|
1104
|
+
for (const s of m.souls) souls.push({ name: s.name, key: soulKeyOf(s), origin: originOf(s), kind: "member", repoKey: s.repoKey, commit: s.commit, ...teamsHere(s), private: s.private, path: s.path, work: s.definition.work ?? null, description: s.definition.description ?? null, ...soulDefaults(s) });
|
|
1105
|
+
for (const c of m.capabilities) capabilities.push({ name: c.name, origin: originOf(c), kind: "member", repoKey: c.repoKey, commit: c.commit, private: c.private, path: c.path, layer: c.manifest.layer ?? null, version: c.manifest.version ?? null });
|
|
1091
1106
|
}
|
|
1092
1107
|
for (const ext of discovery.external || []) {
|
|
1093
1108
|
const s = ext.soul;
|
|
1094
|
-
souls.push({ name: s.name, origin: `external ${s.repoKey} @ ${short(s.commit)}`, kind: "external", repoKey: s.repoKey, commit: s.commit,
|
|
1109
|
+
souls.push({ name: s.name, key: soulKeyOf(s), origin: `external ${s.repoKey} @ ${short(s.commit)}`, kind: "external", repoKey: s.repoKey, commit: s.commit, ...teamsHere(s), private: s.private, path: s.path, work: s.definition.work ?? null, description: s.definition.description ?? null, ...soulDefaults(s) });
|
|
1095
1110
|
}
|
|
1096
1111
|
for (const s of discovery.packageSouls || []) {
|
|
1097
|
-
souls.push({ name: s.name, qualifiedName: s.qualifiedName, origin: originOf(s), kind: "package", package: s.package, version: s.version, repoKey: s.repoKey, commit: s.commit,
|
|
1112
|
+
souls.push({ name: s.name, key: soulKeyOf(s), qualifiedName: s.qualifiedName, origin: originOf(s), kind: "package", package: s.package, version: s.version, repoKey: s.repoKey, commit: s.commit, ...teamsHere(s), private: false, path: s.path, work: s.definition.work ?? null, description: s.definition.description ?? null, ...soulDefaults(s) });
|
|
1098
1113
|
}
|
|
1099
1114
|
for (const [id, entry] of Object.entries(lock?.packages || {})) {
|
|
1100
|
-
for (const name of entry.capabilities) capabilities.push({ name, origin: originOf({ package: id, version: entry.version }), kind: "package", package: id, version: entry.version, commit: entry.commit,
|
|
1115
|
+
for (const name of entry.capabilities) capabilities.push({ name, origin: originOf({ package: id, version: entry.version }), kind: "package", package: id, version: entry.version, commit: entry.commit, private: false });
|
|
1101
1116
|
}
|
|
1102
1117
|
const byName = (a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : a.origin < b.origin ? -1 : a.origin > b.origin ? 1 : 0);
|
|
1103
1118
|
souls.sort(byName);
|
|
@@ -1108,7 +1123,7 @@ function workspaceItems(discovery, lock) {
|
|
|
1108
1123
|
/** Membership rows for `sync` / `workspace status`. */
|
|
1109
1124
|
function memberRows(discovery) {
|
|
1110
1125
|
return discovery.members.map((m) => ({
|
|
1111
|
-
key: m.key, name: memberLabel(m.key), commit: m.commit ?? null, confirmed: m.confirmed, status: m.confirmed ? "confirmed" : m.reason, detail: m.detail ?? null,
|
|
1126
|
+
key: m.key, name: memberLabel(m.key), commit: m.commit ?? null, confirmed: m.confirmed, status: m.confirmed ? "confirmed" : m.reason, detail: m.detail ?? null,
|
|
1112
1127
|
souls: m.souls.map((s) => s.name), capabilities: m.capabilities.map((c) => c.name), publishes: m.publishes ?? null,
|
|
1113
1128
|
}));
|
|
1114
1129
|
}
|
|
@@ -1179,7 +1194,7 @@ async function performSync(ctx, bail, { onDiscovered } = {}) {
|
|
|
1179
1194
|
const members = memberRows(discovery);
|
|
1180
1195
|
const packages = packageRows(lock);
|
|
1181
1196
|
const changes = resolved.changes;
|
|
1182
|
-
const items = workspaceItems(discovery, lock);
|
|
1197
|
+
const items = workspaceItems(discovery, lock, ctx.local, ctx.deploymentDir);
|
|
1183
1198
|
const report = { syncApi: 1, automations: automationCounts(automations), standalone: discovery.standalone === true || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, url: discovery.url, commit: discovery.commit, observedAt: discovery.observedAt, local: ctx.localPath, lock: lockFile }, members, packages, changes, problems, warnings: discovery.warnings ?? [] };
|
|
1184
1199
|
return { report, lock, discovery, items, lockFile, problems };
|
|
1185
1200
|
}
|
|
@@ -1215,10 +1230,8 @@ function printSyncReport(ctx, synced) {
|
|
|
1215
1230
|
const packageSouls = items.souls.filter((s) => s.kind === "package");
|
|
1216
1231
|
const disabledHere = items.souls.filter(isDisabled);
|
|
1217
1232
|
console.log(`souls ${items.souls.length} discovered (${memberSouls.length} members, ${externalSouls.length} external, ${packageSouls.length ? `${packageSouls.length} package, ` : ""}${disabledHere.length} disabled here) · ${repoOwned.length} private capabilit${repoOwned.length === 1 ? "y" : "ies"}${repoOwned.length ? ` (${repoOwned.map((c) => `${c.name}, ${memberLabel(c.repoKey)} only`).join("; ")})` : ""}`);
|
|
1218
|
-
const
|
|
1219
|
-
|
|
1220
|
-
for (const c of items.capabilities.filter((c) => c.kind === "member")) { const t = teams.get(c.team) || { souls: 0, capabilities: 0 }; t.capabilities++; teams.set(c.team, t); }
|
|
1221
|
-
console.log(`teams ${[...teams.entries()].sort(([a], [b]) => (a < b ? -1 : 1)).map(([team, n]) => `${team} ${n.souls} soul${n.souls === 1 ? "" : "s"}${n.capabilities ? `, ${n.capabilities} capabilit${n.capabilities === 1 ? "y" : "ies"}` : ""}`).join(" · ") || "(none)"}`);
|
|
1233
|
+
const sharedTeams = Object.keys(discovery.workspace?.teams || {}).sort();
|
|
1234
|
+
console.log(`teams ${sharedTeams.length ? `${sharedTeams.join(", ")} (shared)` : "(no shared teams)"} · this deployment's: oats teams`);
|
|
1222
1235
|
const ac = report.automations;
|
|
1223
1236
|
if (ac.triggers || ac.schedules) console.log(`automations ${ac.triggers} trigger${ac.triggers === 1 ? "" : "s"}, ${ac.schedules} schedule${ac.schedules === 1 ? "" : "s"} in the members (oats trigger list · oats schedule list)`);
|
|
1224
1237
|
for (const p of synced.problems ?? discovery.problems) console.log(`problem ${p.code} ${p.repoKey ? `${memberLabel(p.repoKey)}:` : ""}${p.path} ${p.message}`);
|
|
@@ -1416,32 +1429,46 @@ async function workspaceCmd() {
|
|
|
1416
1429
|
const locked = new Set(packages.map((p) => p.id));
|
|
1417
1430
|
const unsynced = declared.filter((id) => !locked.has(id));
|
|
1418
1431
|
const stale = packages.filter((p) => !declared.includes(p.id)).map((p) => p.id);
|
|
1419
|
-
const result = { workspaceStatusApi: 1, standalone: standalone || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, url: discovery.url, commit: discovery.commit, observedAt: discovery.observedAt, local: ctx.localPath, teams:
|
|
1432
|
+
const result = { workspaceStatusApi: 1, standalone: standalone || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, url: discovery.url, commit: discovery.commit, observedAt: discovery.observedAt, local: ctx.localPath, teams: sharedTeamRows(discovery) }, members, packages, declaredPackages: declared, unsynced, stale, external: (discovery.external || []).map((e) => ({ source: e.source, soul: e.soul.name })), problems: discovery.problems, warnings: [...(discovery.warnings ?? [])] };
|
|
1420
1433
|
// Workspace automations (0.29.0), from the snapshot `oats sync` took, placed on this host.
|
|
1421
1434
|
const actx = scopeAutomations(ctx.deploymentDir, {});
|
|
1422
1435
|
result.automations = { host: actx.host.name, snapshot: actx.snapshot ? { takenAt: actx.snapshot.takenAt, problems: actx.snapshot.problems.length } : null, rows: [...actx.triggers, ...actx.schedules].map((a) => ({ kind: a.kind, id: a.id, runsOn: a.runsOn, owner: a.owner, runsHere: a.placement.runsHere, reason: a.placement.reason, enabledHere: a.placement.enabledHere, origin: a.origin, ...(a.invalid ? { invalid: a.invalid } : {}) })) };
|
|
1436
|
+
// Automation trust (0.30): a workspace automation placed here but not trusted, and a trust entry
|
|
1437
|
+
// that names none, are warnings the operator acts on in oats-local.yaml.
|
|
1438
|
+
for (const a of [...actx.triggers, ...actx.schedules].filter((x) => x.placement.reason === "untrusted")) {
|
|
1439
|
+
result.warnings.push({ code: "automation-untrusted", kind: a.kind, id: a.id, message: `${a.kind} ${a.id} is declared for this host (${a.runsOn}, as ${a.owner}) but not trusted here, so it does not run`, remedy: A.trustRemedy(a.id) });
|
|
1440
|
+
}
|
|
1441
|
+
for (const entry of A.staleTrust(actx.host, [...actx.triggers, ...actx.schedules])) {
|
|
1442
|
+
result.warnings.push({ code: "automation-trust-stale", entry, message: `oats-local.yaml automations.trust names ${entry}, which is no workspace trigger or schedule${actx.snapshot ? "" : " (no automations snapshot yet: run oats sync)"}; its member may not have synced yet` });
|
|
1443
|
+
}
|
|
1423
1444
|
await workspaceStatusFacts(result, discovery, lock, ctx);
|
|
1424
1445
|
if (JSON_MODE) { jsonOk(result); return; }
|
|
1425
1446
|
console.log(`workspace ${workspaceName(discovery)} (${discovery.key} @ ${short(discovery.commit)}) local ${shortPath(ctx.localPath)}\n`);
|
|
1426
1447
|
if (standalone) console.log(` (${standaloneNote(discovery)})\n`);
|
|
1427
1448
|
console.log("Members:");
|
|
1428
|
-
printTable(["member", "status", "commit", "
|
|
1449
|
+
printTable(["member", "status", "commit", "souls", "capabilities", "publishes"], members.map((m) => [m.name, m.status, short(m.commit), m.souls.join(",") || "—", m.capabilities.join(",") || "—", m.publishes ? `${m.publishes.package} v${m.publishes.version ?? "?"}` : "—"]));
|
|
1429
1450
|
for (const m of members.filter((m) => !m.confirmed)) console.log(` ${m.name}: ${m.detail}`);
|
|
1430
1451
|
console.log("\nPackages:");
|
|
1431
1452
|
if (!packages.length) console.log(unsynced.length ? ` (none locked yet — \`oats sync\` resolves ${unsynced.join(", ")})` : " (none)");
|
|
1432
1453
|
else printTable(["package", "version", "source", "commit", "capabilities", "souls"], packages.map((p) => [p.id, p.version, p.source, short(p.commit), p.capabilities.join(","), p.souls.join(",") || "—"]));
|
|
1433
1454
|
if (packages.length && unsynced.length) console.log(` declared but not locked (run \`oats sync\`): ${unsynced.join(", ")}`);
|
|
1434
1455
|
if (stale.length) console.log(` locked but no longer declared (run \`oats sync\`): ${stale.join(", ")}`);
|
|
1435
|
-
if (result.external.length) console.log(`\nExternal: ${result.external.map((e) => `${e.soul} (${e.source.replace(/@([0-9a-f]{40})$/, (_, o) => `@${short(o)}`)}
|
|
1456
|
+
if (result.external.length) console.log(`\nExternal: ${result.external.map((e) => `${e.soul} (${e.source.replace(/@([0-9a-f]{40})$/, (_, o) => `@${short(o)}`)})`).join(" ")}`);
|
|
1436
1457
|
if (result.automations.rows.length) {
|
|
1437
1458
|
console.log(`\nAutomations (this host: ${result.automations.host ?? "unnamed — set host.name in oats-local.yaml"}):`);
|
|
1438
1459
|
printTable(["kind", "id", "runs on", "owner", "here"], result.automations.rows.map((r) => [r.kind, r.id, r.runsOn, r.owner, r.runsHere ? "runs here" : r.invalid ? "invalid" : r.reason ?? "disabled here"]));
|
|
1439
1460
|
}
|
|
1440
1461
|
if (discovery.problems.length) { console.log("\nProblems:"); for (const p of discovery.problems) console.log(` ${p.code} ${p.repoKey ? `${memberLabel(p.repoKey)}:` : ""}${p.path} ${p.message}`); }
|
|
1441
|
-
if (
|
|
1462
|
+
if (result.warnings.length) { console.log("\nWarnings:"); for (const w of result.warnings) console.log(` ${w.code} ${w.message}${w.remedy ? `\n ${w.remedy}` : ""}`); }
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
/** The committed (shared) teams as `workspace status` reports them: rows in label order (team model v2). */
|
|
1466
|
+
function sharedTeamRows(discovery) {
|
|
1467
|
+
const teams = discovery.standalone === true ? {} : discovery.workspace?.teams || {};
|
|
1468
|
+
return Object.keys(teams).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0)).map((label) => ({ label, team: typeof teams[label]?.team === "string" ? teams[label].team : null, description: typeof teams[label]?.description === "string" ? teams[label].description : null }));
|
|
1442
1469
|
}
|
|
1443
1470
|
|
|
1444
|
-
/** `oats workspace status` facts (feature desktop-facts): the workspace
|
|
1471
|
+
/** `oats workspace status` facts (feature desktop-facts): the workspace defaults as rows; this
|
|
1445
1472
|
* computer (member clones and the rule that found each, disabled souls, the lock); file locations with
|
|
1446
1473
|
* browsable urls; each catalog package's newer version, when the official catalog has one. */
|
|
1447
1474
|
async function workspaceStatusFacts(result, discovery, lock, ctx) {
|
|
@@ -1453,7 +1480,6 @@ async function workspaceStatusFacts(result, discovery, lock, ctx) {
|
|
|
1453
1480
|
result.defaults = {
|
|
1454
1481
|
slots: Object.fromEntries(LAYERS.map((l) => [l, slot(defaults[l])])),
|
|
1455
1482
|
capabilities: rows(defaults.capabilities),
|
|
1456
|
-
byTeam: Object.fromEntries(Object.entries(defaults.byTeam && typeof defaults.byTeam === "object" ? defaults.byTeam : {}).map(([label, t]) => [label, { capabilities: rows(t?.capabilities) }])),
|
|
1457
1483
|
};
|
|
1458
1484
|
result.workspace.file = discovery.standalone === true ? null : { path: "oats-workspace.yaml", url: browseUrl(discovery.key, discovery.commit, "oats-workspace.yaml") };
|
|
1459
1485
|
for (const m of result.members) Object.assign(m, { url: browseUrl(m.key, m.commit), membershipFile: { path: "oats-membership.yaml", url: browseUrl(m.key, m.commit, "oats-membership.yaml") } });
|
|
@@ -1474,6 +1500,88 @@ async function workspaceStatusFacts(result, discovery, lock, ctx) {
|
|
|
1474
1500
|
}
|
|
1475
1501
|
}
|
|
1476
1502
|
|
|
1503
|
+
/** The deployment's team facts for the team verbs: oats-local.yaml and the committed shared teams
|
|
1504
|
+
* (the workspace host read now; none in a standalone view). */
|
|
1505
|
+
async function teamsContext(bail) {
|
|
1506
|
+
const ctx = workspaceContext(bail);
|
|
1507
|
+
let workspace = null, workspaceKey = null;
|
|
1508
|
+
if (!(typeof ctx.local.standalone === "string" && ctx.local.standalone)) {
|
|
1509
|
+
try { const { observeWorkspace } = await import("../lib/workspace.mjs"); const obs = await observeWorkspace(ctx.local.workspace, { remoteOptions: ctx.remoteOptions }); workspace = obs.workspace; workspaceKey = obs.key; }
|
|
1510
|
+
catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details ?? e.provenance); throw e; }
|
|
1511
|
+
}
|
|
1512
|
+
return { deployment: ctx.deploymentDir, localPath: ctx.localPath, local: ctx.local, workspace, workspaceKey, remoteOptions: ctx.remoteOptions };
|
|
1513
|
+
}
|
|
1514
|
+
const labelsFlag = (name) => { const v = valueFlag(name); return v === undefined ? [] : String(v).split(",").map((l) => l.trim()).filter(Boolean); };
|
|
1515
|
+
const positional = (i) => (args[i] !== undefined && !args[i].startsWith("--") ? args[i] : undefined);
|
|
1516
|
+
|
|
1517
|
+
/** `oats teams [--json] | add <label> --team <id> [--description <d>] | remove <label> | default <label>` —
|
|
1518
|
+
* this deployment's teams (team model v2). Config only: never a provider call. */
|
|
1519
|
+
async function teamsCmd() {
|
|
1520
|
+
const bail = (code, msg, details) => (JSON_MODE ? jsonFail(code, msg, details) : die(msg));
|
|
1521
|
+
const usage = "usage: oats teams [--json] | oats teams add <label> --team <id> [--description <d>] | oats teams remove <label> | oats teams default <label> [--dir <deployment>] [--json]";
|
|
1522
|
+
const sub = positional(1);
|
|
1523
|
+
if (sub !== undefined && !["add", "remove", "default"].includes(sub)) bail("E_USAGE", usage);
|
|
1524
|
+
const label = sub ? positional(2) : undefined;
|
|
1525
|
+
if (sub && label === undefined) bail("E_BAD_ARGS", `oats teams ${sub} needs a team label — ${usage}`);
|
|
1526
|
+
const ctx = await teamsContext(bail);
|
|
1527
|
+
const V = await import("../lib/teams-verbs.mjs");
|
|
1528
|
+
let result = null;
|
|
1529
|
+
try {
|
|
1530
|
+
if (sub === "add") result = V.teamsAdd(ctx, label, { team: valueFlag("team"), description: valueFlag("description") });
|
|
1531
|
+
else if (sub === "remove") result = V.teamsRemove(ctx, label);
|
|
1532
|
+
else if (sub === "default") result = V.teamsDefault(ctx, label);
|
|
1533
|
+
} catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details); throw e; }
|
|
1534
|
+
const doc = V.teamsDocument(result ? { ...ctx, local: result.local } : ctx);
|
|
1535
|
+
if (JSON_MODE) { jsonOk(result ? { ...doc, changed: result.changed } : doc); return; }
|
|
1536
|
+
if (result) console.log(result.changed ? `${sub === "add" ? `Declared team ${label}` : sub === "remove" ? `Removed team ${label}` : `The default team is now ${label}`} in ${shortPath(ctx.localPath)}` : "Nothing to change");
|
|
1537
|
+
console.log(`default ${doc.defaultTeam ?? "(none)"}`);
|
|
1538
|
+
if (!doc.teams.length) console.log("teams (none: `oats aweb setup` creates them, or `oats teams add <label> --team <id>`)");
|
|
1539
|
+
else printTable(["team", "id", "from", ""], doc.teams.map((t) => [t.label, t.team ?? "(no id yet)", t.from, t.default ? "default" : ""]));
|
|
1540
|
+
const souls = Object.entries(doc.souls.teams).map(([k, l]) => `${k}: ${l.join(",")}`);
|
|
1541
|
+
if (souls.length) console.log(`souls ${souls.join(" · ")}`);
|
|
1542
|
+
const defaults = Object.entries(doc.souls.default).map(([k, l]) => `${k}: ${l}`);
|
|
1543
|
+
if (defaults.length) console.log(`defaults ${defaults.join(" · ")}`);
|
|
1544
|
+
for (const p of doc.problems) console.log(`${p.severity === "failure" ? "problem" : "warning"} ${p.code} ${p.message} — ${p.fix}`);
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
/** `oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <label> | --clear-default] [--json]` —
|
|
1548
|
+
* which teams a soul belongs to here, and why (team model v2). Config only. */
|
|
1549
|
+
async function soulCmd() {
|
|
1550
|
+
const bail = (code, msg, details) => (JSON_MODE ? jsonFail(code, msg, details) : die(msg));
|
|
1551
|
+
const usage = "usage: oats soul teams <soul>|'*' [--add <label>[,…]] [--remove <label>[,…]] [--default <label> | --clear-default] [--dir <deployment>] [--json]";
|
|
1552
|
+
if (positional(1) !== "teams") bail("E_USAGE", usage);
|
|
1553
|
+
const name = positional(2);
|
|
1554
|
+
if (name === undefined) bail("E_BAD_ARGS", `oats soul teams needs a soul (or '*' for every soul) — ${usage}`);
|
|
1555
|
+
const edit = { add: labelsFlag("add"), remove: labelsFlag("remove"), setDefault: valueFlag("default") ?? null, clearDefault: args.includes("--clear-default") };
|
|
1556
|
+
const ctx = workspaceContext(bail);
|
|
1557
|
+
let teamsCtx, soul = "*", key = "*";
|
|
1558
|
+
if (name === "*") teamsCtx = await teamsContext(bail);
|
|
1559
|
+
else {
|
|
1560
|
+
// The soul is named as for spawn: E_SOUL_UNKNOWN / E_SOUL_AMBIGUOUS; package souls come from the lock.
|
|
1561
|
+
let lock = null;
|
|
1562
|
+
try { lock = existsSync(join(ctx.deploymentDir, LOCK_FILE)) ? readLock(ctx.deploymentDir) : null; } catch (e) { return bail(e.code || "E_LOCK_SCHEMA", e.message, e.details); }
|
|
1563
|
+
let discovery, entry;
|
|
1564
|
+
try {
|
|
1565
|
+
const { discoverOrStandalone, findSoulEntry } = await import("../lib/instance-resolution.mjs");
|
|
1566
|
+
discovery = await discoverOrStandalone(ctx.local, { lock, deployment: ctx.deploymentDir, remoteOptions: ctx.remoteOptions });
|
|
1567
|
+
entry = findSoulEntry(discovery, name);
|
|
1568
|
+
} catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details ?? e.provenance); throw e; }
|
|
1569
|
+
soul = entry.name; key = soulKeyOf(entry);
|
|
1570
|
+
teamsCtx = { deployment: ctx.deploymentDir, localPath: ctx.localPath, local: ctx.local, workspace: discovery.standalone === true ? null : discovery.workspace, workspaceKey: discovery.key ?? null };
|
|
1571
|
+
}
|
|
1572
|
+
const V = await import("../lib/teams-verbs.mjs");
|
|
1573
|
+
const mutating = edit.add.length || edit.remove.length || edit.setDefault !== null || edit.clearDefault;
|
|
1574
|
+
let result = null, doc;
|
|
1575
|
+
try {
|
|
1576
|
+
if (mutating) result = V.soulTeamsEdit(teamsCtx, key, edit);
|
|
1577
|
+
doc = V.soulTeamsDocument(result ? { ...teamsCtx, local: result.local } : teamsCtx, { soul, key });
|
|
1578
|
+
} catch (e) { if (typeof e?.code === "string" && e.code.startsWith("E_")) return bail(e.code, e.message, e.details); throw e; }
|
|
1579
|
+
if (JSON_MODE) { jsonOk(result ? { ...doc, changed: result.changed } : doc); return; }
|
|
1580
|
+
if (result) console.log(result.changed ? `Updated the teams of ${key === "*" ? "every soul" : key} in ${shortPath(teamsCtx.localPath)}` : "Nothing to change");
|
|
1581
|
+
console.log(`${key === "*" ? "every soul" : key} on this computer: default ${doc.defaultTeam ? `${doc.defaultTeam.label} (${doc.defaultTeam.from})` : "(none)"}`);
|
|
1582
|
+
if (doc.teams.length) printTable(["team", "id", "from", "why"], doc.teams.map((t) => [t.default ? `${t.label} (default)` : t.label, t.team ?? "(no id yet)", t.from, t.via.join(",")]));
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1477
1585
|
/** `oats capabilities` / `oats souls` [--dir] [--json] — contract §6. */
|
|
1478
1586
|
async function itemsCmd(kind) {
|
|
1479
1587
|
const bail = (code, msg, details) => (JSON_MODE ? jsonFail(code, msg, details) : die(msg));
|
|
@@ -1481,7 +1589,7 @@ async function itemsCmd(kind) {
|
|
|
1481
1589
|
const discovery = await discoverForCli(ctx, bail);
|
|
1482
1590
|
let lock;
|
|
1483
1591
|
try { lock = readLock(ctx.deploymentDir); } catch (e) { return bail(e.code || "E_LOCK_SCHEMA", e.message, e.details); }
|
|
1484
|
-
const items = workspaceItems(discovery, lock)[kind];
|
|
1592
|
+
const items = workspaceItems(discovery, lock, ctx.local, ctx.deploymentDir)[kind];
|
|
1485
1593
|
// One command's reads at a commit are shared: many souls resolve over the same manifests and listings.
|
|
1486
1594
|
const remote = memoizedRemote(remoteModule);
|
|
1487
1595
|
if (kind === "capabilities") await capabilityFacts(items, discovery, lock, ctx, remote);
|
|
@@ -1490,8 +1598,8 @@ async function itemsCmd(kind) {
|
|
|
1490
1598
|
if (JSON_MODE) { jsonOk({ [`${kind}Api`]: 1, standalone: standalone || undefined, workspace: { name: workspaceName(discovery), key: discovery.key, commit: discovery.commit }, [kind]: items, problems: discovery.problems }); return; }
|
|
1491
1599
|
console.log(`${kind} of workspace ${workspaceName(discovery)} (${discovery.key} @ ${short(discovery.commit)})${standalone ? ` — ${standaloneNote(discovery)}` : ""}\n`);
|
|
1492
1600
|
if (!items.length) console.log(" (none)");
|
|
1493
|
-
else if (kind === "souls") printTable(["name", "origin", "
|
|
1494
|
-
else printTable(["name", "origin", "
|
|
1601
|
+
else if (kind === "souls") printTable(["name", "origin", "teams here", "work"], items.map((s) => [s.name, s.origin, s.teams === null ? "(invalid: oats teams)" : s.teams.map((t) => (t.default ? `${t.label}*` : t.label)).join(",") || "—", s.work ?? "—"]));
|
|
1602
|
+
else printTable(["name", "origin", "layer"], items.map((c) => [c.private ? `${c.name} (repo-owned)` : c.name, c.origin, c.layer ?? "—"]));
|
|
1495
1603
|
const unsynced = Object.keys(discovery.workspace?.packages || {}).filter((id) => !lock.packages[id]);
|
|
1496
1604
|
if (kind === "capabilities" && unsynced.length) console.log(`\n package capabilities of ${unsynced.join(", ")} appear after \`oats sync\``);
|
|
1497
1605
|
}
|
|
@@ -1635,6 +1743,16 @@ function soulRepoLabel(a, ws) {
|
|
|
1635
1743
|
const moved = stamp.current && stamp.commit && stamp.current !== stamp.commit ? ` (member now @ ${short7(stamp.current)})` : "";
|
|
1636
1744
|
return `${memberLabel(stamp.repoKey)} @ ${short7(stamp.commit)}${moved}`;
|
|
1637
1745
|
}
|
|
1746
|
+
/** A roster agent's soul key: the qualified name of a package soul, else the recorded agent (soul) name;
|
|
1747
|
+
* null when no instance records a workspace soul. Exactly soulKeyOf for the soul it was spawned from. */
|
|
1748
|
+
function agentSoulKey(a) {
|
|
1749
|
+
for (const i of a.instances || []) {
|
|
1750
|
+
const soul = i?.workspace?.soul;
|
|
1751
|
+
if (!soul || typeof soul !== "object") continue;
|
|
1752
|
+
return soul.package && typeof soul.qualifiedName === "string" ? soul.qualifiedName : (typeof i.agent === "string" ? i.agent : a.name);
|
|
1753
|
+
}
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1638
1756
|
const short7 = (oid) => (typeof oid === "string" ? oid.slice(0, 7) : "?");
|
|
1639
1757
|
|
|
1640
1758
|
async function status() {
|
|
@@ -1647,6 +1765,9 @@ async function status() {
|
|
|
1647
1765
|
const verbose = args.includes("--verbose");
|
|
1648
1766
|
const problems = legacyLayoutProblems(root);
|
|
1649
1767
|
if (args.includes("--json")) {
|
|
1768
|
+
// The soul key (feature launch-preference): what souls.teams / souls.default / souls.launch and
|
|
1769
|
+
// `oats soul teams <key>` use — from the instances' records, so it needs no remote.
|
|
1770
|
+
for (const a of data) a.key = agentSoulKey(a);
|
|
1650
1771
|
if (ws) for (const a of data) {
|
|
1651
1772
|
const stamp = ws.souls.get(a.name);
|
|
1652
1773
|
if (stamp) a.soulSource = { repoKey: stamp.repoKey, commit: stamp.commit, path: stamp.path, ...(stamp.current ? { current: stamp.current, status: stamp.current === stamp.commit ? "current" : "moved" } : {}) };
|
|
@@ -1763,7 +1884,7 @@ async function spawnCmd() {
|
|
|
1763
1884
|
if (!agent || soulFetched || agent._dir !== dirname(soulDir)) agent = findAgent(root, soulName);
|
|
1764
1885
|
}
|
|
1765
1886
|
if (!agent) bail("E_SOUL_UNKNOWN", `soul "${name}" was fetched to ${shortPath(soulDir)} but is not readable as a soul there`);
|
|
1766
|
-
note(`(workspace soul: "${name}" from ${wsPrepared.soulEntry.repoKey} @ ${String(wsPrepared.soulEntry.commit).slice(0, 12)}${wsPrepared.
|
|
1887
|
+
note(`(workspace soul: "${name}" from ${wsPrepared.soulEntry.repoKey} @ ${String(wsPrepared.soulEntry.commit).slice(0, 12)}${wsPrepared.resolution.defaultTeam ? `, team ${wsPrepared.resolution.defaultTeam.label}` : ""}${soulFetched ? "; soul source fetched" : ""})`);
|
|
1767
1888
|
} catch (e) {
|
|
1768
1889
|
// Standalone (decisions 10/25): the ONLY package request is the kernel's own
|
|
1769
1890
|
// default; when the catalog cannot name it, say so instead of "add it to packages:"
|
|
@@ -1921,7 +2042,7 @@ async function spawnCmd() {
|
|
|
1921
2042
|
// A launch refusal (configuration, executable, environment reference,
|
|
1922
2043
|
// model, harness) is a fact about the selection, not a spawn-mechanism
|
|
1923
2044
|
// failure: it keeps its own code so a GUI can act on it.
|
|
1924
|
-
if (typeof e?.code === "string" && /^E_LAUNCH_|^E_MODEL_UNKNOWN$|^E_UNSUPPORTED_HARNESS$/.test(e.code)) { bail(e.code, e.message); throw e; }
|
|
2045
|
+
if (typeof e?.code === "string" && /^E_LAUNCH_|^E_MODEL_UNKNOWN$|^E_UNSUPPORTED_HARNESS$|^E_HARNESS_UNAVAILABLE$/.test(e.code)) { bail(e.code, e.message, e.details); throw e; }
|
|
1925
2046
|
// An unmet declared requirement is a fact about the soul's configuration
|
|
1926
2047
|
// (with a remedy), not a spawn-mechanism failure: keep its code and details.
|
|
1927
2048
|
if (e?.code === "E_REQUIREMENT_INACTIVE") { bail(e.code, e.message, { soul: e.soul, capabilities: e.capabilities, context: e.context, remedy: e.remedy }); throw e; }
|
|
@@ -2144,7 +2265,7 @@ async function scheduleCmd() {
|
|
|
2144
2265
|
if (args.includes("--host")) return out(tickHost({ io, dryRun }));
|
|
2145
2266
|
const reg = readRegistry();
|
|
2146
2267
|
const tctx = scopeAutomations(ws(), { io, refresh: !dryRun });
|
|
2147
|
-
const considered = withHostLock(() => [...(tctx.refresh && !tctx.refresh.ok ? [{ workspace: ws(), action: "error", error: `automations refresh: ${tctx.refresh.error}` }] : []), ...tickWorkspace(ws(), { io, reg, wsList: reg.workspaces.includes(ws()) ? reg.workspaces : [...reg.workspaces, ws()], dryRun, ctx: tctx }), ...tickTriggers(ws(), { io, dryRun, ctx: tctx })]);
|
|
2268
|
+
const considered = withHostLock(() => [...(tctx.refresh && !tctx.refresh.ok ? [{ workspace: ws(), action: "error", error: `automations refresh: ${tctx.refresh.error}` }] : []), ...tickWorkspace(ws(), { io, reg, wsList: reg.workspaces.includes(ws()) ? reg.workspaces : [...reg.workspaces, ws()], dryRun, ctx: tctx }), ...tickTriggers(ws(), { io, dryRun, ctx: tctx, reg, wsList: reg.workspaces.includes(ws()) ? reg.workspaces : [...reg.workspaces, ws()] })]);
|
|
2148
2269
|
return out({ tickedAt: new Date().toISOString(), considered, scheduler: schedulerStatus(ws(), io) });
|
|
2149
2270
|
}
|
|
2150
2271
|
case "host": {
|
|
@@ -2200,7 +2321,9 @@ async function triggerCmd() {
|
|
|
2200
2321
|
try {
|
|
2201
2322
|
const { observeWorkspace } = await import("../lib/workspace.mjs");
|
|
2202
2323
|
const { local } = loadLocal(ws());
|
|
2203
|
-
|
|
2324
|
+
// The labels declared here: the committed shared teams and this deployment's local ones.
|
|
2325
|
+
const shared = typeof local.workspace === "string" && !local.standalone ? (await observeWorkspace(local.workspace, { remoteOptions: remoteOptionsFromEnv() })).workspace : null;
|
|
2326
|
+
workspaceTeams = [...teamModel(shared, local).labels.keys()];
|
|
2204
2327
|
} catch { /* reported as unknown (null) */ }
|
|
2205
2328
|
return out(T.testTrigger(ws(), needId(), { workspaceTeams, ctx: ctx() }), (r) => [
|
|
2206
2329
|
`trigger ${r.id}: ${r.ok ? "ready" : "NOT ready"} (nothing was spawned)`,
|
|
@@ -2278,7 +2401,11 @@ async function sessionCmd() {
|
|
|
2278
2401
|
if (launchConfig === true) bad("--launch-config needs a configuration name, or none");
|
|
2279
2402
|
const harness = harnessFlag();
|
|
2280
2403
|
if (harness === true || (harness !== undefined && !LAUNCH_HARNESSES.includes(harness))) bad(`--harness must be one of ${LAUNCH_HARNESSES.join(", ")}`);
|
|
2281
|
-
|
|
2404
|
+
// --reselect-launch (feature launch-preference): the home's launch layers decide again; without it a
|
|
2405
|
+
// recorded launch stays frozen (a changed preference never reaches an existing home on its own).
|
|
2406
|
+
const reselectLaunch = args.includes("--reselect-launch");
|
|
2407
|
+
if (reselectLaunch && launchConfig !== undefined) bad("--reselect-launch applies the launch preferences; --launch-config names one explicitly — choose one");
|
|
2408
|
+
const opts = { model: model || undefined, launchConfig, harness, yolo: yoloFlag(), ...(reselectLaunch ? { reselectLaunch: true } : {}), env: process.env, ...(await homeLiveTeams(home)) };
|
|
2282
2409
|
if (args[1] === "restart") {
|
|
2283
2410
|
const grace = flag("stop-grace");
|
|
2284
2411
|
if (grace !== undefined) { if (grace === true || !/^\d+$/.test(String(grace)) || Number(grace) < 1 || Number(grace) > 300) bad("--stop-grace needs a number of seconds (1..300) to wait for the harness after SIGTERM"); opts.stopGraceMs = Number(grace) * 1000; }
|
|
@@ -2301,8 +2428,13 @@ async function sessionCmd() {
|
|
|
2301
2428
|
const file = flag("file");
|
|
2302
2429
|
if (!file || file === true) throw Object.assign(new Error("session upload needs --file <local path>"), { code: "E_BAD_ARGS" });
|
|
2303
2430
|
result = uploadAttachment({ file, home: home === true ? undefined : home });
|
|
2304
|
-
} else throw Object.assign(new Error("usage: oats session inspect|input|attach|start|restart|receive|upload --home /absolute/home [--text-file path] [--model id] [--launch-config name|none] [--harness pi|claude|codex] [--yolo|--no-yolo] [--stop-grace seconds] [--name file] [--file path] [--json]"), { code: "E_BAD_ARGS" });
|
|
2305
|
-
if (JSON_MODE) jsonOk(result);
|
|
2431
|
+
} else throw Object.assign(new Error("usage: oats session inspect|input|attach|start|restart|receive|upload --home /absolute/home [--text-file path] [--model id] [--launch-config name|none | --reselect-launch] [--harness pi|claude|codex] [--yolo|--no-yolo] [--stop-grace seconds] [--name file] [--file path] [--json]"), { code: "E_BAD_ARGS" });
|
|
2432
|
+
if (JSON_MODE) jsonOk(result);
|
|
2433
|
+
else {
|
|
2434
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2435
|
+
// A start's launch-hook warnings, as spawn prints its own (stderr keeps stdout one JSON document).
|
|
2436
|
+
for (const w of result?.warnings || []) console.error(` WARNING: ${w}`);
|
|
2437
|
+
}
|
|
2306
2438
|
} catch (e) { cmdFail(e.code || "E_SESSION_FAILED", e.message, e.details); }
|
|
2307
2439
|
}
|
|
2308
2440
|
|
|
@@ -2312,8 +2444,8 @@ async function paneCmd() {
|
|
|
2312
2444
|
|
|
2313
2445
|
/** `oats onboard [<dir>] --workspace <repo ref> [--json]` — workspace model v2 (decision 9).
|
|
2314
2446
|
*
|
|
2315
|
-
* Realizes a workspace on this machine in the directory the operator chooses
|
|
2316
|
-
* (docs/
|
|
2447
|
+
* Realizes a workspace on this machine in the directory the operator chooses
|
|
2448
|
+
* (docs/workspaces.md): writes
|
|
2317
2449
|
* `<dir>/oats-local.yaml` naming the workspace, creates `<dir>/agents/` (the
|
|
2318
2450
|
* instance homes), then runs exactly the `oats sync` path — discover over the
|
|
2319
2451
|
* remotes, confirm membership, resolve `packages:`, write `oats-lock.json`. Nothing is installed, no soul
|
|
@@ -2503,7 +2635,11 @@ async function capabilityCommand() {
|
|
|
2503
2635
|
const teamCtx = teamEnv(resolvedFromPrepared(hit.prepared, hit.deployment));
|
|
2504
2636
|
// No home, so no recorded soul: the soul's per-commit copy is OATS_SOUL when a spawn
|
|
2505
2637
|
// already fetched exactly this commit; otherwise the command gets none (never ambient).
|
|
2506
|
-
|
|
2638
|
+
// The agent directory is the soul entry's own (a package soul's is `<package>--<soul>`), never
|
|
2639
|
+
// the bare name, which a same-named member soul's copy may occupy (as instance-inspect does).
|
|
2640
|
+
const { agentDirOf } = await import("../lib/instance-resolution.mjs");
|
|
2641
|
+
const entry = hit.prepared?.soulEntry;
|
|
2642
|
+
const cachedSoul = entry?.commit ? join(hit.deployment, "agents", agentDirOf(entry), "souls", String(entry.commit).slice(0, 12)) : null;
|
|
2507
2643
|
return runManifestCommand({ capability: hit.module.name, ...hit.manifest }, { settings: hit.settings, origins: hit.resolution?.payloadOrigins?.[hit.module.name] }, teamCtx, hit.ensureTree, cachedSoul && existsSync(join(cachedSoul, "soul.yaml")) ? realpathSync(cachedSoul) : undefined);
|
|
2508
2644
|
}
|
|
2509
2645
|
|
|
@@ -2538,8 +2674,8 @@ async function capabilityCommand() {
|
|
|
2538
2674
|
const ws = meta.workspace && typeof meta.workspace === "object" ? meta.workspace : {};
|
|
2539
2675
|
const messaging = (meta.capabilities || []).find((c) => c.layer === "messaging")?.id;
|
|
2540
2676
|
homeMeta = { meta, messaging };
|
|
2541
|
-
homeTeamCtx = (
|
|
2542
|
-
teamCtx = homeTeamCtx(meta
|
|
2677
|
+
homeTeamCtx = (t) => teamEnv({ workspace: { key: ws.key, name: ws.name, deployment: ws.deployment }, teams: t.teams, defaultTeam: t.defaultTeam, teamsSource: t.source });
|
|
2678
|
+
teamCtx = homeTeamCtx({ ...recordedTeams(meta), source: "recorded" });
|
|
2543
2679
|
} else {
|
|
2544
2680
|
// Not inside a home: a v2 deployment (oats-local.yaml in reach) resolves
|
|
2545
2681
|
// through the workspace, exactly as a spawn of --soul would (below).
|
|
@@ -2562,7 +2698,7 @@ async function capabilityCommand() {
|
|
|
2562
2698
|
if (homeMeta && m.capability === homeMeta.messaging) {
|
|
2563
2699
|
const { liveTeams } = await import("../lib/instance-resolution.mjs");
|
|
2564
2700
|
const live = await liveTeams(instanceHome, homeMeta.meta, { remoteOptions: remoteOptionsFromEnv() });
|
|
2565
|
-
teamCtx = homeTeamCtx(live
|
|
2701
|
+
teamCtx = homeTeamCtx(live);
|
|
2566
2702
|
}
|
|
2567
2703
|
return runManifestCommand(m, { settings: capSettings[m.capability] || {}, origins: capOrigins[m.capability] }, teamCtx, () => m._dir, soulDir);
|
|
2568
2704
|
}
|
|
@@ -2687,7 +2823,7 @@ function versionCmd() {
|
|
|
2687
2823
|
// Phase B: `instance-modules` and `spawn-provider-payload` are advertised only once spawn
|
|
2688
2824
|
// runs on resolve/materialize (contract §6); a feature the binary does not implement is
|
|
2689
2825
|
// never listed.
|
|
2690
|
-
console.log(JSON.stringify({ schemaVersion: 1, name: "@awebai/oats", version: OATS_VERSION, desktopApi: 1, harnesses: ["pi", "claude", "codex"], sessionBackends: ["tmux", "herdr"], launchOptions: ["yolo"], remote: ["spawn", "retire", "status", "session", "session-start", "session-restart", "launch-config", "roster", "harvest", "schedule", "session-upload", "operations"], features: ["retire-home", "session-start", "session-restart", "launch-config", "schedule", "session-upload", "operations", "instance-git", "instance-git-remote", "souls-declarations", "lifecycle-plans", "retire-retention", "readiness", "spawn-preview", "instance-events", "instance-events-2", "schedule-history", "schedule-read-2", "spawn-preview-2", "spawn-idempotency", "spawn-idempotency-2", "spawn-apply-2", "workspace-v2", "instance-modules", "spawn-provider-payload", "served-identity", "packages-no-approval", "spawn-name", "settings-origins", "
|
|
2826
|
+
console.log(JSON.stringify({ schemaVersion: 1, name: "@awebai/oats", version: OATS_VERSION, desktopApi: 1, harnesses: ["pi", "claude", "codex"], sessionBackends: ["tmux", "herdr"], launchOptions: ["yolo"], remote: ["spawn", "retire", "status", "session", "session-start", "session-restart", "launch-config", "roster", "harvest", "schedule", "session-upload", "operations"], features: ["retire-home", "session-start", "session-restart", "launch-config", "schedule", "session-upload", "operations", "instance-git", "instance-git-remote", "souls-declarations", "lifecycle-plans", "retire-retention", "readiness", "spawn-preview", "instance-events", "instance-events-2", "schedule-history", "schedule-read-2", "spawn-preview-2", "spawn-idempotency", "spawn-idempotency-2", "spawn-apply-2", "workspace-v2", "instance-modules", "spawn-provider-payload", "served-identity", "packages-no-approval", "spawn-name", "settings-origins", "team-model-2", "settings-declared", "capabilities-private", "layers-from", "harness", "package-souls", "triggers", "automations", "desktop-facts", "launch-preference"], automationsApi: A.AUTOMATIONS_API, workspaceApi: 2, instanceGitApi: 1, spawnApplyApi: 1, soulsApi: 2, lifecycleApi: 1, readinessApi: 2, spawnPreviewApi: 2, eventsApi: 2, scheduleHistoryApi: 3, scheduleApi: SCHEDULE_API, operationsApi: 2 }));
|
|
2691
2827
|
return;
|
|
2692
2828
|
}
|
|
2693
2829
|
console.log(`@awebai/oats ${OATS_VERSION} (desktop API v1)`);
|
|
@@ -3039,7 +3175,7 @@ async function serverRouteCmd() {
|
|
|
3039
3175
|
// blame` pointing at the commit that last changed each command.
|
|
3040
3176
|
const TYPED_CLI_FAILURES = new Set(["unsafe-config-key", "unsafe-config-value"]);
|
|
3041
3177
|
/** Removed 0.24 verbs → their v2 replacement (workspace model v2, decision 5). Checked before capability dispatch. */
|
|
3042
|
-
const REMOVED_VERBS = { prepare: "`oats onboard` / `oats sync` to set up a workspace, and `oats spawn <soul> --preview` to see what a spawn would resolve (the captured/portable path was removed in 0.26)", create: "author souls/<name>/soul.yaml + AGENTS.md in a member repository, then `oats sync`", type: "the soul's own soul.yaml in its member repository (agent types were a classic config block)",
|
|
3178
|
+
const REMOVED_VERBS = { prepare: "`oats onboard` / `oats sync` to set up a workspace, and `oats spawn <soul> --preview` to see what a spawn would resolve (the captured/portable path was removed in 0.26)", create: "author souls/<name>/soul.yaml + AGENTS.md in a member repository, then `oats sync`", type: "the soul's own soul.yaml in its member repository (agent types were a classic config block)", install: "oats sync", restore: "oats sync", init: "oats-local.yaml + oats sync", use: "soul.yaml capabilities: { <cap>: { from } } + workspace defaults", trust: "declaring the package in packages: (package approval was removed; oats sync locks commit + integrity)", list: "oats workspace status | oats capabilities", catalog: "oats package add <id> <version> (bare versions resolve through package-catalog.json)", remove: "oats package remove <id>", migrate: "a rebuild (no migration: docs/design/2026-09-23-workspace-module-contracts.md)", config: "oats-local.yaml (host settings) and oats-workspace.yaml (shared)", inject: "injection overrides are not part of the workspace model yet; edit the capability inject in its member repo" };
|
|
3043
3179
|
try {
|
|
3044
3180
|
// The captured/portable path was removed in 0.26 (lead decisions on (e), D2/D3):
|
|
3045
3181
|
// its selectors and an inherited captured context are refused, never quietly
|
|
@@ -3095,6 +3231,8 @@ else if (cmd === "sync") await syncCmd();
|
|
|
3095
3231
|
else if (cmd === "package") await packageCmd();
|
|
3096
3232
|
else if (cmd === "workspace") await workspaceCmd();
|
|
3097
3233
|
else if (cmd === "capabilities" || cmd === "souls") await itemsCmd(cmd);
|
|
3234
|
+
else if (cmd === "teams") await teamsCmd();
|
|
3235
|
+
else if (cmd === "soul") await soulCmd();
|
|
3098
3236
|
else if (cmd === "status") await status();
|
|
3099
3237
|
else if (cmd === "pane") await paneCmd();
|
|
3100
3238
|
else if (cmd === "version" || cmd === "--version" || cmd === "-v") versionCmd();
|
|
@@ -3218,6 +3356,9 @@ Usage:
|
|
|
3218
3356
|
oats session start --home <absolute-home> start a STOPPED instance again in its existing home
|
|
3219
3357
|
[--model m] [--launch-config n|none] (recorded recipe as is; a selection re-resolves it
|
|
3220
3358
|
[--harness r] [--yolo|--no-yolo] against the scope; a named configuration is a unit)
|
|
3359
|
+
[--reselect-launch] applies the launch preferences now (oats-local.yaml
|
|
3360
|
+
souls.launch, the soul's launch); a changed preference
|
|
3361
|
+
never reaches an existing home without it or a respawn
|
|
3221
3362
|
oats session restart --home <abs-home> stop the running harness (SIGTERM, bounded wait,
|
|
3222
3363
|
[same flags] [--stop-grace <s>] never escalated) and start it again in place under
|
|
3223
3364
|
the same lock; a stop that is not observed is
|
|
@@ -3225,7 +3366,7 @@ Usage:
|
|
|
3225
3366
|
[--model <m>] [--json] (same identity, worktree, notes and launch env; no
|
|
3226
3367
|
spawn hooks); --model replaces the recorded model
|
|
3227
3368
|
for this and later starts; a live harness is refused
|
|
3228
|
-
oats spawn <agent> [--task <text>]
|
|
3369
|
+
oats spawn <agent> [--task <text> | --task-file <f>] spawn an instance (tmux/Herdr; --no-launch
|
|
3229
3370
|
[--purpose <slug>] [--repo <r>] = scaffold only); the agent is a workspace
|
|
3230
3371
|
[--parent <instance>] soul or a capability-defined agent
|
|
3231
3372
|
[--relation child|sibling|parent|unrelated] --relation + --relative-to anchor the
|
|
@@ -3233,7 +3374,9 @@ Usage:
|
|
|
3233
3374
|
[--relative-root <agents-root>] disambiguates same-named team anchors
|
|
3234
3375
|
[--work worktree|checkout|attached|workspace|directory] = sugar for --relative-to X --relation
|
|
3235
3376
|
[--work-dir <owner-work>] [--harness pi|claude|codex] [--backend tmux|herdr] [--herdr-socket <path>] [--yolo|--no-yolo] [--model <m>] [--branch <b>] child (default: unrelated, top-level)
|
|
3236
|
-
[--no-launch] [--json]
|
|
3377
|
+
[--no-launch] [--json] without --launch-config/--harness the launch is the
|
|
3378
|
+
soul's preference: oats-local.yaml souls.launch.<soul>,
|
|
3379
|
+
then souls.launch."*", then the soul's launch:, then pi
|
|
3237
3380
|
with team: declared, unknown souls
|
|
3238
3381
|
resolve across the team scope's repos
|
|
3239
3382
|
directory: owned home/work, config context may
|
|
@@ -3297,7 +3440,13 @@ Usage:
|
|
|
3297
3440
|
its own repo's souls) + the locked packages
|
|
3298
3441
|
oats souls [--dir <d>] [--json] every soul of every confirmed member + external souls
|
|
3299
3442
|
(souls have no private mode), with origin
|
|
3300
|
-
(member <key> @ <commit> | package <id> v<ver>) and
|
|
3443
|
+
(member <key> @ <commit> | package <id> v<ver>) and its
|
|
3444
|
+
teams on this deployment
|
|
3445
|
+
oats teams [--json] | add <label> --team <id> [--description <d>] | remove <label>
|
|
3446
|
+
| default <label> [--dir <d>] this deployment's teams (shared + local), the
|
|
3447
|
+
default; add/remove/default edit oats-local.yaml
|
|
3448
|
+
oats soul teams <soul>|'*' [--add a,b] [--remove a,b] [--default <l> | --clear-default]
|
|
3449
|
+
[--dir <d>] [--json] which teams a soul (or every soul) belongs to here
|
|
3301
3450
|
oats instance git <instance> [--home <abs>] [--dir <d>] [--json]
|
|
3302
3451
|
read-only Git observation of the instance's work
|
|
3303
3452
|
tree: branch, status (renames kept), ahead/behind
|