@awebai/oats 0.24.1 → 0.24.3
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 +227 -19
- package/capabilities/oats-aweb/bin/oats-aweb-binding.mjs +11 -0
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +26 -0
- package/capabilities/oats-aweb/lib/binding-wire.mjs +214 -0
- package/capabilities/oats-aweb/lib/captured-execution.mjs +91 -0
- package/capabilities/oats-aweb/lib/captured-native.mjs +91 -0
- package/capabilities/oats-aweb/lib/invocation-shape.mjs +135 -0
- package/capabilities/oats-aweb/lib/portable-binding.mjs +146 -0
- package/capabilities/oats-aweb/lib/session-readiness.mjs +56 -0
- package/capabilities/oats-aweb/oats.json +12 -3
- package/docs/design/2026-09-20-redesign-program-board.md +28 -15
- package/docs/design/2026-09-20-workspace-onboarding-public.md +73 -7
- package/docs/first-team.md +42 -0
- package/docs/layers.md +1 -1
- package/docs/official-marketplace.md +14 -9
- package/docs/packages.md +11 -7
- package/docs/release-notes/v0.24.2.md +21 -0
- package/docs/release-notes/v0.24.3.md +13 -0
- package/docs/souls-and-instances.md +25 -0
- package/docs/workspace-adoption.md +94 -65
- package/injects/oats-portable.md +4 -0
- package/lib/core.mjs +65 -19
- package/lib/portable-onboarding.mjs +3 -3
- package/lib/prepared-bindings.mjs +21 -8
- package/lib/provider-binding-broker.mjs +6 -1
- package/lib/setup-expert-source.mjs +76 -0
- package/package-catalog.json +7 -4
- package/package.json +3 -1
- package/souls/oats-setup-expert/AGENTS.md +60 -0
- package/souls/oats-setup-expert/soul.yaml +14 -0
|
@@ -37,24 +37,34 @@ export function prepareProviderBindings({seed,plan,manifests,declarations},invok
|
|
|
37
37
|
const normalized=new Map(),problems=[],requirements=[...plan.requirements],candidates=[...plan.candidates];
|
|
38
38
|
const settingsFor=id=>Object.fromEntries(Object.entries(plan.settings[id] ?? {}).map(([name,key])=>[name,plan.choices[key].value]));
|
|
39
39
|
const run=(id,phase,input)=>invoke({artifacts:seed.artifacts,capability:id,phase,settings:settingsFor(id),input});
|
|
40
|
-
const
|
|
40
|
+
const originsFor=id=>[...new Map((plan.capabilities[id]?.choiceKeys ?? []).flatMap(key=>{
|
|
41
|
+
const choice=plan.choices[key];return choice?.selectedBy?[choice.selectedBy]:[];
|
|
42
|
+
}).map(origin=>[canonicalJson(origin),origin])).values()];
|
|
43
|
+
const problem=(slot,id,code,message,origins=originsFor(id))=>({code,message,origins,slot,capability:id});
|
|
44
|
+
const refuse=(slot,id,phase,error)=>problem(slot,id,safeCodes.has(error?.code)?error.code:'provider-not-qualified',
|
|
45
|
+
`${id} ${slot} ${phase} binding could not be prepared`); // Never provider free text.
|
|
41
46
|
for (const [id,manifest] of manifests) {
|
|
42
47
|
if (!Object.hasOwn(seed.artifacts.capabilities,id) || !manifest.layer) continue;
|
|
43
|
-
if (plan.providers[manifest.layer] !== id) problems.push(
|
|
48
|
+
if (plan.providers[manifest.layer] !== id) problems.push(problem(manifest.layer,id,'needs-configuration','fundamental capability must be selected in its own layer'));
|
|
44
49
|
}
|
|
45
|
-
if (problems.length) return {plan,seed,problems};
|
|
46
50
|
for (const [slot,id] of Object.entries(plan.providers)) {
|
|
47
51
|
if (id === null) continue;
|
|
48
52
|
const manifest=manifests.get(id);
|
|
49
|
-
if (manifest?.layer !== slot
|
|
53
|
+
if (manifest?.layer !== slot) { problems.push(problem(slot,id,'provider-not-qualified',`${id} does not declare the selected ${slot} layer`));continue; }
|
|
54
|
+
if (!manifest.binding) { problems.push(problem(slot,id,'provider-not-qualified',`${id}@${manifest.version} declares no binding interface; ${slot} cannot be prepared`));continue; }
|
|
50
55
|
try {
|
|
51
56
|
const value=run(id,'normalize',{declarations,context:seed.context});
|
|
52
57
|
normalized.set(slot,{id,value}); requirements.push(...value.requirements);candidates.push(...value.candidates);
|
|
53
|
-
} catch(error) { problems.push(refuse(error)); }
|
|
58
|
+
} catch(error) { problems.push(refuse(slot,id,'normalize',error)); }
|
|
54
59
|
}
|
|
55
|
-
|
|
60
|
+
// One unsupported slot must not mask another slot's normalized requirements
|
|
61
|
+
// or invalid choices. The same resolver still decides every value.
|
|
56
62
|
const resolved=resolveChoices({requirements,candidates});
|
|
57
|
-
|
|
63
|
+
for (const item of resolved.problems) {
|
|
64
|
+
const slot=Object.keys(plan.providers).find(slot=>bindingField(item.key,slot));
|
|
65
|
+
if (!slot) throw oatsError('invalid-binding-output','provider choice problem has no owned binding slot');
|
|
66
|
+
problems.push({...item,slot,capability:plan.providers[slot]});
|
|
67
|
+
}
|
|
58
68
|
const nextPlan={...plan,...resolved,requirements,candidates},bindings=Object.create(null);
|
|
59
69
|
let messagingChoice={schemaVersion:1,enabled:false};
|
|
60
70
|
const known=[];
|
|
@@ -64,6 +74,7 @@ export function prepareProviderBindings({seed,plan,manifests,declarations},invok
|
|
|
64
74
|
}
|
|
65
75
|
const witnessed=origin=>bindingOriginWitnessed(origin,declarations) || known.some(candidate=>same(candidate,origin));
|
|
66
76
|
for (const [slot,{id,value}] of normalized) {
|
|
77
|
+
if (resolved.problems.some(item=>bindingField(item.key,slot))) continue;
|
|
67
78
|
try {
|
|
68
79
|
const choices=Object.fromEntries(Object.entries(resolved.choices).filter(([key])=>bindingField(key,slot)));
|
|
69
80
|
const bound=run(id,'bind',{model:value.model,choices,context:seed.context});
|
|
@@ -72,7 +83,9 @@ export function prepareProviderBindings({seed,plan,manifests,declarations},invok
|
|
|
72
83
|
}
|
|
73
84
|
bindings[slot]=bound.binding;
|
|
74
85
|
if (slot === 'messaging') messagingChoice=bound.messagingChoice;
|
|
75
|
-
} catch(error) { problems.push(refuse(error)); }
|
|
86
|
+
} catch(error) { problems.push(refuse(slot,id,'bind',error)); }
|
|
76
87
|
}
|
|
88
|
+
nextPlan.problems=problems;
|
|
89
|
+
nextPlan.status=problems.some(item=>item.code==='requirement-conflict')?'conflict':problems.length?'needs-configuration':'resolved';
|
|
77
90
|
return {plan:nextPlan,seed:{...seed,choices:resolved.choices,bindings,messagingChoice},problems};
|
|
78
91
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/** Execute only an approved retained provider command. Preparation does not need
|
|
2
2
|
* a fabricated complete resolution merely to obtain its binding. */
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
+
import { realpathSync } from 'node:fs';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
4
6
|
import { canonicalJson } from './portable-values.mjs';
|
|
5
7
|
import { objectAt } from './portable-shape.mjs';
|
|
6
8
|
import { validateArtifactSet } from './resolution-shape.mjs';
|
|
@@ -12,6 +14,9 @@ import { validateBindingInterface } from './provider-binding.mjs';
|
|
|
12
14
|
import { BINDING_LIMITS, validateBindingRequest, decodeBindingResponse } from './provider-binding-wire.mjs';
|
|
13
15
|
import { oatsError } from './errors.mjs';
|
|
14
16
|
|
|
17
|
+
// Same kernel-owned CLI locator as lifecycle hooks; never caller env or PATH.
|
|
18
|
+
const CLI_BIN=fileURLToPath(new URL('../bin/oats.mjs',import.meta.url));
|
|
19
|
+
|
|
15
20
|
export function invokeProviderBinding(options,codecs) {
|
|
16
21
|
canonicalJson(options);
|
|
17
22
|
objectAt(options,['deployment','artifacts','capability','phase','settings','input','timeoutMs'],['deployment','artifacts','capability','phase','settings','input']);
|
|
@@ -46,7 +51,7 @@ export function invokeProviderBinding(options,codecs) {
|
|
|
46
51
|
const [script,...args]=spec.trim().split(/\s+/),file=codecs.executable(manifest,script);
|
|
47
52
|
if (!file) throw oatsError('resource-not-found','provider codec executable is unavailable');
|
|
48
53
|
const environment=Object.fromEntries(Object.entries(process.env).filter(([key])=>! /^(OATS_|OAS_|PI_)/.test(key)));
|
|
49
|
-
Object.assign(environment,{OATS_CAPABILITY:capability,OATS_CAPABILITY_ROOT:root,OATS_SETTINGS:canonicalJson(settings,BINDING_LIMITS)});
|
|
54
|
+
Object.assign(environment,{OATS_CAPABILITY:capability,OATS_CAPABILITY_ROOT:root,OATS_SETTINGS:canonicalJson(settings,BINDING_LIMITS),OATS_CLI_BIN:realpathSync(CLI_BIN)});
|
|
50
55
|
let result;
|
|
51
56
|
try {
|
|
52
57
|
result=spawnSync(process.execPath,[file,...args],{cwd:root,env:environment,input:canonicalJson(request,BINDING_LIMITS),
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/** Read the setup edition as data for a CLASSIC local bootstrap copy.
|
|
2
|
+
* No provider code, workspace activation, enrollment or captured identity. */
|
|
3
|
+
import { lstatSync, mkdtempSync, readFileSync, readdirSync, readlinkSync, realpathSync, rmSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { parsePortableSoul } from './portable-soul.mjs';
|
|
8
|
+
import { parseRepositorySource, parsePortableSource } from './source-spec.mjs';
|
|
9
|
+
import { createRepositoryTransaction } from './repository-observation.mjs';
|
|
10
|
+
import { createWorkspaceDiscovery } from './workspace-discovery.mjs';
|
|
11
|
+
import { packageIntegrity } from './core.mjs';
|
|
12
|
+
import { oatsError } from './errors.mjs';
|
|
13
|
+
|
|
14
|
+
export const SETUP_EXPERT = 'oats-setup-expert';
|
|
15
|
+
export const SETUP_CAPABILITIES = Object.freeze(['oats.core', 'oats.setup']);
|
|
16
|
+
const EXPORT = `souls/${SETUP_EXPERT}`;
|
|
17
|
+
function validateEdition(bytes) {
|
|
18
|
+
const { declaration: soul } = parsePortableSoul(bytes);
|
|
19
|
+
const required = soul.requires || {}, caps = required.capabilities || {};
|
|
20
|
+
if (soul.name !== SETUP_EXPERT || soul.work !== 'directory'
|
|
21
|
+
|| Object.keys(required).some(key => key !== 'capabilities')
|
|
22
|
+
|| Object.keys(caps).length !== 2 || SETUP_CAPABILITIES.some(id => caps[id]?.source !== 'repo:oats-package' || Object.keys(caps[id]).some(key => key !== 'source'))
|
|
23
|
+
|| ['knowledge', 'messaging', 'tasks'].some(slot => soul.defaults?.[slot] !== 'none')
|
|
24
|
+
|| Object.keys(soul.defaults || {}).some(key => !['knowledge', 'messaging', 'tasks'].includes(key))
|
|
25
|
+
|| soul.knowledge || soul.teams?.length || soul.resources?.length || soul.yolo === true || soul.backend || soul['launch-config']) {
|
|
26
|
+
throw oatsError('needs-configuration', 'classic setup bootstrap needs the provider-independent directory edition with only oats.core/oats.setup; use explicit portable preparation for other requirements');
|
|
27
|
+
}
|
|
28
|
+
return soul;
|
|
29
|
+
}
|
|
30
|
+
function repositoryRequest(source) {
|
|
31
|
+
if (typeof source !== 'string' || source.includes('#')) throw oatsError('invalid-source', '--workspace needs a Git repository source, optionally @revision, without a package fragment');
|
|
32
|
+
try { return { source: parseRepositorySource(source).normalized }; }
|
|
33
|
+
catch {
|
|
34
|
+
const parsed = parsePortableSource(source);
|
|
35
|
+
if (parsed.kind !== 'git') throw oatsError('invalid-source', '--workspace needs an explicit Git repository source');
|
|
36
|
+
return { source: `git:${parsed.url}`, revision: parsed.selector };
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function loadSetupExpertEdition(workspace, repositoryOptions = {}) {
|
|
41
|
+
if (workspace === undefined) {
|
|
42
|
+
const root = fileURLToPath(new URL(`../${EXPORT}/`, import.meta.url));
|
|
43
|
+
return { declaration: validateEdition(readFileSync(join(root, 'soul.yaml'))), instructions: readFileSync(join(root, 'AGENTS.md'), 'utf8'),
|
|
44
|
+
source: { kind: 'packaged-definition', captured: false }, packageIntegrity: null };
|
|
45
|
+
}
|
|
46
|
+
const request = repositoryRequest(workspace), scratch = realpathSync(mkdtempSync(join(tmpdir(), 'oats-setup-source-'))), owned = lstatSync(scratch);
|
|
47
|
+
let transaction;
|
|
48
|
+
try {
|
|
49
|
+
transaction = createRepositoryTransaction({ ...repositoryOptions, directory: scratch, accessContextKey: 'explicit-setup-source' });
|
|
50
|
+
const discovery = createWorkspaceDiscovery(transaction), origin = { kind: 'operator', document: { kind: 'operator', id: 'oats-onboard' }, pointer: '/workspace' };
|
|
51
|
+
const observed = transaction.observe(request.source, { ...request, origin });
|
|
52
|
+
const workspaceDoc = transaction.readFile(observed, 'oats-workspace.yaml', { optional: true });
|
|
53
|
+
const view = workspaceDoc ? discovery.readWorkspace({ ...request, origin }) : null;
|
|
54
|
+
const reference = view?.parsed.imports.find(item => item.alias === SETUP_EXPERT)
|
|
55
|
+
?? { source: request.source, revision: observed.source.commit, soul: EXPORT, alias: SETUP_EXPERT };
|
|
56
|
+
// A selected workspace import never falls back if its exact source fails.
|
|
57
|
+
const imported = discovery.importSoul(reference, { origin });
|
|
58
|
+
if (reference.adoption) throw oatsError('needs-configuration', 'classic setup copies an edition only; provider adoption values require the portable preparation path');
|
|
59
|
+
const declaration = validateEdition(transaction.readFile(imported.observation, imported.definition).bytes);
|
|
60
|
+
const projection = join(scratch, 'edition');
|
|
61
|
+
transaction.materialize(imported.observation, imported.roots, projection);
|
|
62
|
+
const soulRoot = join(projection, dirname(imported.definition)), body = join(soulRoot, 'AGENTS.md'), alias = join(soulRoot, 'CLAUDE.md');
|
|
63
|
+
if (!lstatSync(body).isFile() || !lstatSync(alias).isSymbolicLink() || readlinkSync(alias) !== 'AGENTS.md'
|
|
64
|
+
|| readdirSync(soulRoot).some(name => !['soul.yaml', 'AGENTS.md', 'CLAUDE.md'].includes(name))) {
|
|
65
|
+
throw oatsError('source-incomplete', 'classic setup edition must have canonical AGENTS.md/CLAUDE.md and no omitted private skill or knowledge trees');
|
|
66
|
+
}
|
|
67
|
+
return { declaration, instructions: readFileSync(body, 'utf8'), packageIntegrity: packageIntegrity(join(projection, 'oats-package')),
|
|
68
|
+
source: { kind: 'exported-edition-copy', source: imported.reference.source, revision: imported.observation.source.commit,
|
|
69
|
+
path: imported.reference.soul, workspaceRevision: view?.source.commit ?? null, captured: false } };
|
|
70
|
+
} finally {
|
|
71
|
+
transaction?.close();
|
|
72
|
+
const current = lstatSync(scratch);
|
|
73
|
+
if (!current.isDirectory() || current.dev !== owned.dev || current.ino !== owned.ino) throw oatsError('source-unavailable', 'setup source scratch ownership changed');
|
|
74
|
+
rmSync(scratch, { recursive: true });
|
|
75
|
+
}
|
|
76
|
+
}
|
package/package-catalog.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
},
|
|
9
9
|
"oats.aweb": {
|
|
10
10
|
"url": "https://github.com/awebai/oats-aweb.git",
|
|
11
|
-
"ref": "v1.
|
|
11
|
+
"ref": "v1.11.0",
|
|
12
12
|
"path": "oats-package"
|
|
13
13
|
},
|
|
14
14
|
"oats.jira": {
|
|
@@ -31,9 +31,9 @@
|
|
|
31
31
|
"ref": "v1.0.0",
|
|
32
32
|
"path": "oats-package"
|
|
33
33
|
},
|
|
34
|
-
"oats.
|
|
34
|
+
"oats.framework": {
|
|
35
35
|
"url": "https://github.com/awebai/oats.git",
|
|
36
|
-
"ref": "
|
|
36
|
+
"ref": "oats-framework/v1.1.1",
|
|
37
37
|
"path": "oats-package"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
@@ -66,6 +66,9 @@
|
|
|
66
66
|
"oas.review": {
|
|
67
67
|
"package": "oats.dev",
|
|
68
68
|
"capability": "oats.review"
|
|
69
|
-
}
|
|
69
|
+
},
|
|
70
|
+
"oats.knowledge-theory": "oats.framework",
|
|
71
|
+
"oats.core": "oats.framework",
|
|
72
|
+
"oats.setup": "oats.framework"
|
|
70
73
|
}
|
|
71
74
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.24.
|
|
3
|
+
"version": "0.24.3",
|
|
4
4
|
"description": "OATS (Open Agent Team Specification) — durable souls, disposable instances, targetable capability packages, and the runtime-neutral oats CLI/kernel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|
|
@@ -36,6 +36,8 @@
|
|
|
36
36
|
"docs/",
|
|
37
37
|
"README.md",
|
|
38
38
|
"package-catalog.json",
|
|
39
|
+
"souls/oats-setup-expert/soul.yaml",
|
|
40
|
+
"souls/oats-setup-expert/AGENTS.md",
|
|
39
41
|
"packages/record/bin/",
|
|
40
42
|
"packages/record/lib/",
|
|
41
43
|
"packages/record/docs/",
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# OATS Setup Expert
|
|
2
|
+
|
|
3
|
+
Help an operator turn an empty deployment into a deliberately configured OATS
|
|
4
|
+
workspace. Explain the next small decision, inspect the existing state, obtain
|
|
5
|
+
approval for effects, and verify the result before moving on. Do not replace
|
|
6
|
+
working deployments or turn setup into an implicit enrollment operation.
|
|
7
|
+
|
|
8
|
+
## Your supplied procedures
|
|
9
|
+
|
|
10
|
+
- Load **oats-workspace-setup** for workspace/source discovery and adoption:
|
|
11
|
+
declare, inspect, prepare, approve, scaffold and start are different steps.
|
|
12
|
+
- Load **oats-config** for version-scoped classic configuration and targeting;
|
|
13
|
+
never use its cascade to fill a missing captured input.
|
|
14
|
+
- Load **oats-packages** for official package discovery, acquisition, exact locks,
|
|
15
|
+
executable approval and updates.
|
|
16
|
+
- Load **oats-operate** for lifecycle, directory boundaries and supported CLI
|
|
17
|
+
operations; load **oats-souls** for source editions, roster and relations.
|
|
18
|
+
|
|
19
|
+
Use the procedures actually included in your composition. Do not fetch a current
|
|
20
|
+
skill or invent a command when an older installed version lacks a feature.
|
|
21
|
+
|
|
22
|
+
## Setup sequence
|
|
23
|
+
|
|
24
|
+
1. Establish the operator's intended deployment, work target and workspace/source
|
|
25
|
+
separately. Inspect existing configuration, locks and souls before proposing
|
|
26
|
+
changes. A workspace is a shared definition, not a shared live runtime.
|
|
27
|
+
2. Explain `oats-workspace.yaml` and each member's separate `oats.yaml` exports
|
|
28
|
+
and backlink. Check reciprocal observations; discovery is neither membership
|
|
29
|
+
enrollment nor capability activation. Pin imports only after a source is
|
|
30
|
+
published at a real reviewed revision; never invent a future commit or tag.
|
|
31
|
+
3. Select capabilities and their exact sources with the operator. New souls
|
|
32
|
+
declare removable `oats.core` explicitly. Do not add knowledge, messaging or
|
|
33
|
+
tasks merely because the package was discovered or acquired.
|
|
34
|
+
4. Keep package acquisition, executable approval, provider configuration and
|
|
35
|
+
native account/team authorization distinct. Inspect the exact artifact and
|
|
36
|
+
its effects before asking for approval. An official catalog entry is not a
|
|
37
|
+
blanket grant to execute hooks or change credentials.
|
|
38
|
+
5. Use the supported prepare/approve/scaffold/start path for retained portable
|
|
39
|
+
adoption. Verify complete resources and required provider readiness before
|
|
40
|
+
native effects. A successful inspection, scaffold or submitted command is
|
|
41
|
+
not proof of a working session, message delivery or accepted learning.
|
|
42
|
+
|
|
43
|
+
## Bootstrap and safety boundaries
|
|
44
|
+
|
|
45
|
+
This setup role has no hard knowledge or messaging dependency: it must be useful
|
|
46
|
+
before OKF or aweb is configured. Its defaults permit none. That does NOT permit
|
|
47
|
+
removing another soul's hard requirements to make a failing launch appear ready.
|
|
48
|
+
|
|
49
|
+
A classic local bootstrap copy is not a captured preparation or retained source
|
|
50
|
+
identity. Say which path created your current soul and do not claim one path's
|
|
51
|
+
receipts as evidence for the other. Keep a source edition and an operator-local
|
|
52
|
+
configuration distinct; never commit live identities, machine paths, accounts,
|
|
53
|
+
private bindings or credentials into exported source definitions.
|
|
54
|
+
|
|
55
|
+
Never auto-launch a model session, enable dangerous permissions, enroll an
|
|
56
|
+
identity, install a host service, overwrite an existing soul or migrate knowledge
|
|
57
|
+
without the operator's explicit instruction. Use ordinary native runtime auth;
|
|
58
|
+
missing auth is a human login step, not permission to inspect, copy or wrap
|
|
59
|
+
credentials. Preserve existing instances, pending jobs, locks and failed receipts.
|
|
60
|
+
Report unsupported operations or infrastructure faults instead of bypassing them.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
schemaVersion: 1
|
|
2
|
+
name: oats-setup-expert
|
|
3
|
+
description: Guide an operator through OATS workspace adoption and explicit capability setup.
|
|
4
|
+
work: directory
|
|
5
|
+
requires:
|
|
6
|
+
capabilities:
|
|
7
|
+
oats.core:
|
|
8
|
+
source: repo:oats-package
|
|
9
|
+
oats.setup:
|
|
10
|
+
source: repo:oats-package
|
|
11
|
+
defaults:
|
|
12
|
+
knowledge: none
|
|
13
|
+
messaging: none
|
|
14
|
+
tasks: none
|