feat: add filesystem resource layout and docs query tool; replace NEW_FEAT with FEAT

This commit is contained in:
2026-03-07 16:31:53 +00:00
parent 8590c1ff31
commit b7a0211fb5
11 changed files with 295 additions and 94 deletions

86
FEAT.md Normal file
View File

@@ -0,0 +1,86 @@
# FEAT — Yonexus Feature List
## Existing Features
### Core Model & Storage
- Organization / Department / Team / Agent / Identity / Supervisor data model
- In-memory runtime with JSON persistence (`data/org.json`)
- Import/export of structure data
### Authorization
- Role model: `org_admin`, `dept_admin`, `team_lead`, `agent`
- `authorize(action, actor, scope)` permission check
- Registrar whitelist (`registrars`) and bootstrap registration support
### Core APIs
- `createOrganization(actor, name)`
- `createDepartment(actor, name, orgId)`
- `createTeam(actor, name, deptId)`
- `registerAgent(actor, agentId, name, roles?)`
- `assignIdentity(actor, agentId, deptId, teamId, meta)`
- `setSupervisor(actor, agentId, supervisorId, deptId?)`
- `whoami(agentId)`
- `queryAgents(actor, scope, query)`
### Query & Search
- Query ops: `eq`, `contains`, `regex`
- Schema `queryable` whitelist enforcement
- Pagination (`limit`, `offset`)
- Basic query performance optimization (regex cache + ordered filter eval)
### Management & Audit
- `renameDepartment`, `renameTeam`, `migrateTeam`, `deleteDepartment`, `deleteTeam`
- Structured errors via `YonexusError`
- In-memory audit log (`listAuditLogs`)
### Scope Memory
- Scope memory adapter:
- `scope_memory.put(scopeId, text, metadata)`
- `scope_memory.search(scopeId, query, limit)`
### Developer Experience
- `README.md` + `README.zh.md`
- Example data (`examples/sample-data.json`)
- Demo script (`scripts/demo.ts`)
- Smoke test (`tests/smoke.ts`)
---
## New Features (from NEW_FEAT)
### 1) Filesystem Resource Layout
Data-only filesystem tree under:
- `${openclaw dir}/yonexus/organizations/<org-name>/...`
Auto-create (idempotent):
- On `createOrganization`:
- `teams/`, `docs/`, `notes/`, `knowledge/`, `rules/`, `lessons/`, `workflows/`
- On `createTeam`:
- `teams/<team-name>/agents/`, `docs/`, `notes/`, `knowledge/`, `rules/`, `lessons/`, `workflows/`
- On `assignIdentity`:
- `teams/<team-name>/agents/<agent-id>/docs|notes|knowledge|rules|lessons|workflows`
### 2) Document Query Tool
New API:
- `getDocs(scope, topic, keyword)`
Parameters:
- `scope`: `organization | department | team | agent`
- `topic`: `docs | notes | knowledge | rules | lessons | workflows`
- `keyword`: regex string
Behavior:
- Read-only search by filename regex under filesystem resources
- Structured output:
- `----ORG`
- `----DEPT`
- `----TEAM`
- `----AGENT`
- Invalid regex returns structured error (`YonexusError: VALIDATION_ERROR`)
## Notes
- `${openclaw dir}` resolution order:
1. `YonexusOptions.openclawDir`
2. `OPENCLAW_DIR` env
3. `${HOME}/.openclaw`
- Plugin code is not written into `${openclaw dir}/yonexus`; only data folders/files are used there.

View File

@@ -1,83 +0,0 @@
# NEW_FEAT — Filesystem Resource Layout
## Summary
Introduce filesystem-backed resource folders under `${openclaw dir}/yonexus` (data-only, no plugin code). These resources are created automatically when orgs/teams/identities are created.
## Root
- `${openclaw dir}/yonexus/organizations/<org-name>/`
### Organization folders
Create on **create_organization**:
```
teams/
docs/
notes/
knowledge/
rules/
lessons/
workflows/
```
### Team folders
Create on **create_team**:
```
${openclaw dir}/yonexus/organizations/<org-name>/teams/<team-name>/
agents/
docs/
notes/
knowledge/
rules/
lessons/
workflows/
```
### Agent folders
Create on **assign_identity**:
```
${openclaw dir}/yonexus/organizations/<org-name>/teams/<team-name>/agents/<agent-id>/
docs/
notes/
knowledge/
rules/
lessons/
workflows/
```
## Notes
- The plugin must not store code in `${openclaw dir}/yonexus`, only data.
- If folders already exist, creation should be idempotent.
---
# NEW_FEAT — Document Query Tool
## Summary
Add a tool to retrieve documentation files based on scope, topic, and keyword.
## Tool
`get_docs(scope, topic, keyword)`
### Parameters
- **scope**: enum `organization | department | team | agent`
- **topic**: enum `docs | notes | knowledge | rules | lessons | workflows`
- **keyword**: string (supports regular expressions)
## Behavior
- Search matching files under `${openclaw dir}/yonexus/organizations/...` based on the given scope/topic.
- `keyword` is applied to filenames (regex supported).
- Output grouped by scope in the following format:
```
----ORG
<file list>
----DEPT
<file list>
----TEAM
<file list>
----AGENT
<file list>
```
- If no match in a group, output the header with no files beneath.
## Notes
- Search should be read-only.
- Pattern errors should return a structured error (invalid regex).

View File

@@ -9,6 +9,7 @@ Yonexus is an OpenClaw plugin for organization hierarchy and agent identity mana
## Features ## Features
- Organization hierarchy: `Organization -> Department -> Team -> Agent` - Organization hierarchy: `Organization -> Department -> Team -> Agent`
- Filesystem-backed resource layout under `${openclaw dir}/yonexus`
- Agent registration and multi-identity assignment - Agent registration and multi-identity assignment
- Supervisor relationship mapping (does **not** imply permissions) - Supervisor relationship mapping (does **not** imply permissions)
- Role-based authorization - Role-based authorization
@@ -71,6 +72,7 @@ npm run demo
## Implemented APIs ## Implemented APIs
Core: Core:
- `createOrganization(actor, name)`
- `createDepartment(actor, name, orgId)` - `createDepartment(actor, name, orgId)`
- `createTeam(actor, name, deptId)` - `createTeam(actor, name, deptId)`
- `registerAgent(actor, agentId, name, roles?)` - `registerAgent(actor, agentId, name, roles?)`
@@ -86,6 +88,9 @@ Management:
- `deleteDepartment(actor, deptId)` - `deleteDepartment(actor, deptId)`
- `deleteTeam(actor, teamId, deptId?)` - `deleteTeam(actor, teamId, deptId?)`
Docs:
- `getDocs(scope, topic, keyword)`
Data & audit: Data & audit:
- `exportData(actor)` - `exportData(actor)`
- `importData(actor, state)` - `importData(actor, state)`

View File

@@ -9,6 +9,7 @@ Yonexus 是一个用于 OpenClaw 的组织结构与 Agent 身份管理插件。
## 功能特性 ## 功能特性
- 组织层级:`Organization -> Department -> Team -> Agent` - 组织层级:`Organization -> Department -> Team -> Agent`
- 基于文件系统的资源目录:`${openclaw dir}/yonexus`
- Agent 注册与多身份Identity管理 - Agent 注册与多身份Identity管理
- 上下级关系Supervisor**不自动赋权** - 上下级关系Supervisor**不自动赋权**
- 基于角色的权限控制 - 基于角色的权限控制
@@ -71,6 +72,7 @@ npm run demo
## 已实现 API ## 已实现 API
核心 API 核心 API
- `createOrganization(actor, name)`
- `createDepartment(actor, name, orgId)` - `createDepartment(actor, name, orgId)`
- `createTeam(actor, name, deptId)` - `createTeam(actor, name, deptId)`
- `registerAgent(actor, agentId, name, roles?)` - `registerAgent(actor, agentId, name, roles?)`
@@ -86,6 +88,9 @@ npm run demo
- `deleteDepartment(actor, deptId)` - `deleteDepartment(actor, deptId)`
- `deleteTeam(actor, teamId, deptId?)` - `deleteTeam(actor, teamId, deptId?)`
文档检索:
- `getDocs(scope, topic, keyword)`
数据与审计: 数据与审计:
- `exportData(actor)` - `exportData(actor)`
- `importData(actor, state)` - `importData(actor, state)`

View File

@@ -10,7 +10,8 @@ const yx = new Yonexus({ dataFile, registrars: ['orion'] });
yx.registerAgent({ agentId: 'orion' }, 'orion', 'Orion', ['org_admin', 'agent']); yx.registerAgent({ agentId: 'orion' }, 'orion', 'Orion', ['org_admin', 'agent']);
yx.registerAgent({ agentId: 'orion' }, 'hangman', 'Hangman', ['agent']); yx.registerAgent({ agentId: 'orion' }, 'hangman', 'Hangman', ['agent']);
const dept = yx.createDepartment({ agentId: 'orion' }, 'Platform', 'org:yonexus'); const org = yx.createOrganization({ agentId: 'orion' }, 'Yonexus');
const dept = yx.createDepartment({ agentId: 'orion' }, 'Platform', org.id);
const team = yx.createTeam({ agentId: 'orion' }, 'Core', dept.id); const team = yx.createTeam({ agentId: 'orion' }, 'Core', dept.id);
yx.assignIdentity({ agentId: 'orion' }, 'orion', dept.id, team.id, { yx.assignIdentity({ agentId: 'orion' }, 'orion', dept.id, team.id, {

View File

@@ -5,6 +5,8 @@ import { YonexusError } from "./models/errors";
import type { import type {
Actor, Actor,
Agent, Agent,
DocsScope,
DocsTopic,
Identity, Identity,
QueryInput, QueryInput,
Scope, Scope,
@@ -15,12 +17,14 @@ import { authorize } from "./permissions/authorize";
import { AuditStore } from "./store/auditStore"; import { AuditStore } from "./store/auditStore";
import { JsonStore } from "./store/jsonStore"; import { JsonStore } from "./store/jsonStore";
import { queryIdentities } from "./tools/query"; import { queryIdentities } from "./tools/query";
import { ResourceLayout } from "./tools/resources";
import { makeId } from "./utils/id"; import { makeId } from "./utils/id";
export interface YonexusOptions { export interface YonexusOptions {
dataFile?: string; dataFile?: string;
schema?: YonexusSchema; schema?: YonexusSchema;
registrars?: string[]; registrars?: string[];
openclawDir?: string;
} }
export class Yonexus { export class Yonexus {
@@ -28,12 +32,19 @@ export class Yonexus {
private readonly registrars: Set<string>; private readonly registrars: Set<string>;
private readonly store: JsonStore; private readonly store: JsonStore;
private readonly audit = new AuditStore(); private readonly audit = new AuditStore();
private readonly resources: ResourceLayout;
constructor(options: YonexusOptions = {}) { constructor(options: YonexusOptions = {}) {
const dataFile = options.dataFile ?? path.resolve(process.cwd(), "data/org.json"); const dataFile = options.dataFile ?? path.resolve(process.cwd(), "data/org.json");
this.store = new JsonStore(dataFile); this.store = new JsonStore(dataFile);
this.schema = options.schema ?? DEFAULT_SCHEMA; this.schema = options.schema ?? DEFAULT_SCHEMA;
this.registrars = new Set(options.registrars ?? []); this.registrars = new Set(options.registrars ?? []);
const openclawDir =
options.openclawDir ??
process.env.OPENCLAW_DIR ??
path.resolve(process.env.HOME ?? process.cwd(), ".openclaw");
this.resources = new ResourceLayout(path.join(openclawDir, "yonexus"));
} }
private log(entry: Omit<AuditLogEntry, "id" | "ts">): void { private log(entry: Omit<AuditLogEntry, "id" | "ts">): void {
@@ -44,9 +55,24 @@ export class Yonexus {
}); });
} }
createOrganization(actor: Actor, name: string) {
authorize("create_organization", actor, {}, this.store);
const orgId = makeId("org", name);
if (this.store.findOrganization(orgId)) {
throw new YonexusError("ALREADY_EXISTS", `organization_exists: ${orgId}`);
}
const org = this.store.addOrganization({ id: orgId, name });
this.resources.ensureOrganization(name);
this.log({ actorId: actor.agentId, action: "create_organization", target: org.id, status: "ok" });
return org;
}
createDepartment(actor: Actor, name: string, orgId: string) { createDepartment(actor: Actor, name: string, orgId: string) {
try { try {
authorize("create_department", actor, { orgId }, this.store); authorize("create_department", actor, { orgId }, this.store);
if (!this.store.findOrganization(orgId)) {
throw new YonexusError("NOT_FOUND", `organization_not_found: ${orgId}`, { orgId });
}
const dept = { id: makeId("dept", name), name, orgId }; const dept = { id: makeId("dept", name), name, orgId };
const result = this.store.addDepartment(dept); const result = this.store.addDepartment(dept);
this.log({ actorId: actor.agentId, action: "create_department", target: result.id, status: "ok" }); this.log({ actorId: actor.agentId, action: "create_department", target: result.id, status: "ok" });
@@ -65,11 +91,15 @@ export class Yonexus {
createTeam(actor: Actor, name: string, deptId: string) { createTeam(actor: Actor, name: string, deptId: string) {
authorize("create_team", actor, { deptId }, this.store); authorize("create_team", actor, { deptId }, this.store);
if (!this.store.findDepartment(deptId)) { const dept = this.store.findDepartment(deptId);
throw new YonexusError("NOT_FOUND", `department_not_found: ${deptId}`, { deptId }); if (!dept) throw new YonexusError("NOT_FOUND", `department_not_found: ${deptId}`, { deptId });
}
const org = this.store.findOrganization(dept.orgId);
if (!org) throw new YonexusError("NOT_FOUND", `organization_not_found: ${dept.orgId}`);
const team = { id: makeId("team", `${deptId}-${name}`), name, deptId }; const team = { id: makeId("team", `${deptId}-${name}`), name, deptId };
const result = this.store.addTeam(team); const result = this.store.addTeam(team);
this.resources.ensureTeam(org.name, name);
this.log({ actorId: actor.agentId, action: "create_team", target: result.id, status: "ok" }); this.log({ actorId: actor.agentId, action: "create_team", target: result.id, status: "ok" });
return result; return result;
} }
@@ -80,9 +110,7 @@ export class Yonexus {
} }
const isBootstrap = this.store.listAgents().length === 0 && actor.agentId === agentId; const isBootstrap = this.store.listAgents().length === 0 && actor.agentId === agentId;
if (!isBootstrap) { if (!isBootstrap) authorize("register_agent", actor, {}, this.store);
authorize("register_agent", actor, {}, this.store);
}
if (this.store.findAgent(agentId)) { if (this.store.findAgent(agentId)) {
throw new YonexusError("ALREADY_EXISTS", `agent_exists: ${agentId}`, { agentId }); throw new YonexusError("ALREADY_EXISTS", `agent_exists: ${agentId}`, { agentId });
@@ -95,8 +123,15 @@ export class Yonexus {
assignIdentity(actor: Actor, agentId: string, deptId: string, teamId: string, meta: Record<string, string>): Identity { assignIdentity(actor: Actor, agentId: string, deptId: string, teamId: string, meta: Record<string, string>): Identity {
authorize("assign_identity", actor, { deptId, teamId }, this.store); authorize("assign_identity", actor, { deptId, teamId }, this.store);
if (!this.store.findAgent(agentId)) throw new YonexusError("NOT_FOUND", `agent_not_found: ${agentId}`); if (!this.store.findAgent(agentId)) throw new YonexusError("NOT_FOUND", `agent_not_found: ${agentId}`);
if (!this.store.findDepartment(deptId)) throw new YonexusError("NOT_FOUND", `department_not_found: ${deptId}`);
if (!this.store.findTeam(teamId)) throw new YonexusError("NOT_FOUND", `team_not_found: ${teamId}`); const dept = this.store.findDepartment(deptId);
if (!dept) throw new YonexusError("NOT_FOUND", `department_not_found: ${deptId}`);
const team = this.store.findTeam(teamId);
if (!team) throw new YonexusError("NOT_FOUND", `team_not_found: ${teamId}`);
const org = this.store.findOrganization(dept.orgId);
if (!org) throw new YonexusError("NOT_FOUND", `organization_not_found: ${dept.orgId}`);
const validatedMeta: Record<string, string> = {}; const validatedMeta: Record<string, string> = {};
for (const [field, value] of Object.entries(meta)) { for (const [field, value] of Object.entries(meta)) {
@@ -111,6 +146,8 @@ export class Yonexus {
teamId, teamId,
meta: validatedMeta meta: validatedMeta
}); });
this.resources.ensureAgent(org.name, team.name, agentId);
this.log({ actorId: actor.agentId, action: "assign_identity", target: result.id, status: "ok" }); this.log({ actorId: actor.agentId, action: "assign_identity", target: result.id, status: "ok" });
return result; return result;
} }
@@ -140,6 +177,10 @@ export class Yonexus {
return queryIdentities(identities, query, this.schema); return queryIdentities(identities, query, this.schema);
} }
getDocs(scope: DocsScope, topic: DocsTopic, keyword: string): string {
return this.resources.getDocs(scope, topic, keyword);
}
renameDepartment(actor: Actor, deptId: string, newName: string) { renameDepartment(actor: Actor, deptId: string, newName: string) {
authorize("create_department", actor, {}, this.store); authorize("create_department", actor, {}, this.store);
const updated = this.store.renameDepartment(deptId, newName); const updated = this.store.renameDepartment(deptId, newName);

View File

@@ -59,6 +59,7 @@ export interface Actor {
} }
export type Action = export type Action =
| "create_organization"
| "create_department" | "create_department"
| "create_team" | "create_team"
| "register_agent" | "register_agent"
@@ -66,6 +67,9 @@ export type Action =
| "set_supervisor" | "set_supervisor"
| "query_agents"; | "query_agents";
export type DocsScope = "organization" | "department" | "team" | "agent";
export type DocsTopic = "docs" | "notes" | "knowledge" | "rules" | "lessons" | "workflows";
export interface Scope { export interface Scope {
orgId?: string; orgId?: string;
deptId?: string; deptId?: string;

View File

@@ -22,6 +22,7 @@ export function authorize(action: Action, actor: Actor, scope: Scope, store: Jso
const agent = hasRole(store, actor, "agent"); const agent = hasRole(store, actor, "agent");
const allowed = const allowed =
(action === "create_organization" && orgAdmin) ||
(action === "create_department" && orgAdmin) || (action === "create_department" && orgAdmin) ||
(action === "create_team" && (orgAdmin || deptAdmin)) || (action === "create_team" && (orgAdmin || deptAdmin)) ||
(action === "assign_identity" && (orgAdmin || deptAdmin || teamLead)) || (action === "assign_identity" && (orgAdmin || deptAdmin || teamLead)) ||

View File

@@ -118,14 +118,30 @@ export class JsonStore {
return this.state.agents.find((a) => a.id === agentId); return this.state.agents.find((a) => a.id === agentId);
} }
findOrganization(orgId: string): Organization | undefined {
return this.state.organizations.find((o) => o.id === orgId);
}
listOrganizations(): Organization[] {
return this.state.organizations;
}
findDepartment(deptId: string): Department | undefined { findDepartment(deptId: string): Department | undefined {
return this.state.departments.find((d) => d.id === deptId); return this.state.departments.find((d) => d.id === deptId);
} }
listDepartments(): Department[] {
return this.state.departments;
}
findTeam(teamId: string): Team | undefined { findTeam(teamId: string): Team | undefined {
return this.state.teams.find((t) => t.id === teamId); return this.state.teams.find((t) => t.id === teamId);
} }
listTeams(): Team[] {
return this.state.teams;
}
listAgents(): Agent[] { listAgents(): Agent[] {
return this.state.agents; return this.state.agents;
} }

111
src/tools/resources.ts Normal file
View File

@@ -0,0 +1,111 @@
import fs from 'node:fs';
import path from 'node:path';
import { YonexusError } from '../models/errors';
import type { DocsScope, DocsTopic } from '../models/types';
import { slug } from '../utils/id';
const TOPICS: DocsTopic[] = ['docs', 'notes', 'knowledge', 'rules', 'lessons', 'workflows'];
function ensureDirs(base: string, dirs: string[]): void {
for (const d of dirs) fs.mkdirSync(path.join(base, d), { recursive: true });
}
export class ResourceLayout {
constructor(private readonly rootDir: string) {}
get organizationsRoot(): string {
return path.join(this.rootDir, 'organizations');
}
orgPath(orgName: string): string {
return path.join(this.organizationsRoot, slug(orgName));
}
teamPath(orgName: string, teamName: string): string {
return path.join(this.orgPath(orgName), 'teams', slug(teamName));
}
agentPath(orgName: string, teamName: string, agentId: string): string {
return path.join(this.teamPath(orgName, teamName), 'agents', slug(agentId));
}
ensureOrganization(orgName: string): void {
const root = this.orgPath(orgName);
ensureDirs(root, ['teams', ...TOPICS]);
}
ensureTeam(orgName: string, teamName: string): void {
const root = this.teamPath(orgName, teamName);
ensureDirs(root, ['agents', ...TOPICS]);
}
ensureAgent(orgName: string, teamName: string, agentId: string): void {
const root = this.agentPath(orgName, teamName, agentId);
ensureDirs(root, TOPICS);
}
private readTopicFiles(topicRoot: string, keyword: string): string[] {
if (!fs.existsSync(topicRoot)) return [];
let re: RegExp;
try {
re = new RegExp(keyword);
} catch {
throw new YonexusError('VALIDATION_ERROR', 'invalid_regex', { keyword });
}
const entries = fs.readdirSync(topicRoot, { withFileTypes: true });
return entries
.filter((e) => e.isFile() && re.test(e.name))
.map((e) => path.join(topicRoot, e.name));
}
getDocs(scope: DocsScope, topic: DocsTopic, keyword: string): string {
const groups: Record<'ORG' | 'DEPT' | 'TEAM' | 'AGENT', string[]> = {
ORG: [],
DEPT: [],
TEAM: [],
AGENT: []
};
const orgsRoot = this.organizationsRoot;
if (!fs.existsSync(orgsRoot)) {
return '----ORG\n----DEPT\n----TEAM\n----AGENT';
}
const orgs = fs.readdirSync(orgsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
for (const orgName of orgs) {
const orgPath = path.join(orgsRoot, orgName);
if (scope === 'organization') {
groups.ORG.push(...this.readTopicFiles(path.join(orgPath, topic), keyword));
}
const teamsRoot = path.join(orgPath, 'teams');
if (!fs.existsSync(teamsRoot)) continue;
const teams = fs.readdirSync(teamsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
for (const teamName of teams) {
const teamPath = path.join(teamsRoot, teamName);
if (scope === 'team') {
groups.TEAM.push(...this.readTopicFiles(path.join(teamPath, topic), keyword));
}
const agentsRoot = path.join(teamPath, 'agents');
if (!fs.existsSync(agentsRoot)) continue;
const agents = fs.readdirSync(agentsRoot, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
for (const agentId of agents) {
if (scope === 'agent') {
groups.AGENT.push(...this.readTopicFiles(path.join(agentsRoot, agentId, topic), keyword));
}
}
}
}
// department folders are not defined in this layout; reserved empty group for compatible output.
const printGroup = (k: 'ORG' | 'DEPT' | 'TEAM' | 'AGENT'): string =>
[`----${k}`, ...groups[k]].join('\n');
return [printGroup('ORG'), printGroup('DEPT'), printGroup('TEAM'), printGroup('AGENT')].join('\n');
}
}

View File

@@ -4,14 +4,17 @@ import fs from 'node:fs';
import { Yonexus } from '../src/index'; import { Yonexus } from '../src/index';
import { YonexusError } from '../src/models/errors'; import { YonexusError } from '../src/models/errors';
const root = path.resolve(process.cwd(), 'data/test-openclaw');
const dataFile = path.resolve(process.cwd(), 'data/test-org.json'); const dataFile = path.resolve(process.cwd(), 'data/test-org.json');
if (fs.existsSync(dataFile)) fs.unlinkSync(dataFile); if (fs.existsSync(dataFile)) fs.unlinkSync(dataFile);
if (fs.existsSync(root)) fs.rmSync(root, { recursive: true, force: true });
const app = new Yonexus({ dataFile, registrars: ['orion'] }); const app = new Yonexus({ dataFile, registrars: ['orion'], openclawDir: root });
app.registerAgent({ agentId: 'orion' }, 'orion', 'Orion', ['org_admin', 'agent']); app.registerAgent({ agentId: 'orion' }, 'orion', 'Orion', ['org_admin', 'agent']);
app.registerAgent({ agentId: 'orion' }, 'u1', 'U1', ['agent']); app.registerAgent({ agentId: 'orion' }, 'u1', 'U1', ['agent']);
const dept = app.createDepartment({ agentId: 'orion' }, 'Eng', 'org:yonexus'); const org = app.createOrganization({ agentId: 'orion' }, 'Yonexus');
const dept = app.createDepartment({ agentId: 'orion' }, 'Eng', org.id);
const team = app.createTeam({ agentId: 'orion' }, 'API', dept.id); const team = app.createTeam({ agentId: 'orion' }, 'API', dept.id);
app.assignIdentity({ agentId: 'orion' }, 'u1', dept.id, team.id, { app.assignIdentity({ agentId: 'orion' }, 'u1', dept.id, team.id, {
git_user_name: 'u1', git_user_name: 'u1',
@@ -25,6 +28,9 @@ const result = app.queryAgents(
); );
assert.equal(result.length, 1); assert.equal(result.length, 1);
const expectedAgentDocsDir = path.join(root, 'yonexus', 'organizations', 'yonexus', 'teams', 'api', 'agents', 'u1', 'docs');
assert.equal(fs.existsSync(expectedAgentDocsDir), true);
let thrown = false; let thrown = false;
try { try {
app.queryAgents( app.queryAgents(
@@ -37,4 +43,12 @@ try {
} }
assert.equal(thrown, true); assert.equal(thrown, true);
let invalidRegexThrown = false;
try {
app.getDocs('agent', 'docs', '[');
} catch (e) {
invalidRegexThrown = e instanceof YonexusError && e.code === 'VALIDATION_ERROR';
}
assert.equal(invalidRegexThrown, true);
console.log('smoke test passed'); console.log('smoke test passed');