fix(init): preserve additional roles selected at the prompt (#765)

This commit is contained in:
dvd233
2026-09-24 10:47:10 +08:00
committed by GitHub
parent cc2772114f
commit 5576b38db7
5 changed files with 65 additions and 6 deletions
+2
View File
@@ -171,6 +171,8 @@ If the repo has role-based skills enabled (i.e. `manifest/roles.yaml` exists), `
- `primaryRole`: the target namespace for skill sync and push by default
- `additionalRoles`: additional skill namespaces to sync
At the role prompt, enter one or more comma-separated role numbers. The first number becomes `primaryRole` and the remaining numbers become `additionalRoles` (for example, `1,3`).
You can also skip the interactive prompts via CLI flags for a fully non-interactive init (suitable for CI/CD or AI agents):
```bash
+2
View File
@@ -165,6 +165,8 @@ teamai init https://github.com/yourorg/yourrepo
- `primaryRole`:默认 skill 同步和推送的目标 namespace
- `additionalRoles`:额外需要同步的 skill namespace
角色提示中可以输入一个或多个用逗号分隔的角色编号。第一个编号会保存为 `primaryRole`,后续编号会保存为 `additionalRoles`(例如 `1,3`)。
也可以通过 CLI 参数跳过交互,实现完全非交互式初始化(适合 CI/CD 或 AI agent):
```bash
+5 -3
View File
@@ -187,9 +187,11 @@ If the repo does not exist yet, `init` offers to create it — accept the prompt
- If `init` detects an unknown GitLab host, it stops and asks you to set
`GITLAB_URL` + `GITLAB_TOKEN`, then retry.
If the repo has roles enabled, `init` may ask for a primary role — pick one with
the user, or pass `--role <id>` for a non-interactive run. Without a terminal
(or with `CI` / `TEAMAI_NONINTERACTIVE` set) `init` never waits: a provider that
If the repo has roles enabled, `init` asks for one or more comma-separated role
numbers when running interactively. The first number is the primary role and the
remaining numbers become additional roles; pass `--role <id>` for a
non-interactive run when only a primary role is needed. Without a terminal (or
with `CI` / `TEAMAI_NONINTERACTIVE` set) `init` never waits: a provider that
would need a browser login fails at once and names the credential to prepare —
a token for GitHub / CNB / GitLab / GitCode, and for TGit a prior `gf auth
login` run in an interactive shell (see Step 3).
+52
View File
@@ -561,6 +561,58 @@ describe('init', () => {
resourceProfileVersion: 1,
}));
});
it('persists later selections as additional roles', async () => {
let cloneDone = false;
pathExistsFn = (p: string) => {
if (p === localPath) return cloneDone;
if (p === path.join(localPath, 'members', 'testuser.yaml')) return false;
return false;
};
mockGfRepoClone.mockImplementation(() => {
cloneDone = true;
});
const mockedLoadTeamConfig = vi.mocked(await import('../config.js')).loadTeamConfig;
mockedLoadTeamConfig
.mockResolvedValueOnce({
team: 'my-team',
repo: 'https://git.woa.com/HyperAI/teamai-test.git',
provider: 'tgit',
reviewers: [],
sharing: {
skills: {},
rules: { enforced: [] },
docs: { localDir: '~/.teamai/docs' },
env: { injectShellProfile: true },
},
toolPaths: {},
} as never)
.mockResolvedValueOnce({
team: 'my-team',
repo: 'https://git.woa.com/HyperAI/teamai-test.git',
provider: 'tgit',
reviewers: [],
sharing: {
skills: {},
rules: { enforced: [] },
docs: { localDir: '~/.teamai/docs' },
env: { injectShellProfile: true },
},
toolPaths: {},
} as never);
questionAnswers = ['n', '1,3'];
await init({ repo: 'https://git.woa.com/HyperAI/teamai-test.git', scope: 'user' });
expect(saveLocalConfig).toHaveBeenCalledWith(expect.objectContaining({
primaryRole: 'hai',
additionalRoles: ['thpc'],
resourceProfileVersion: 1,
}));
});
});
describe('deploys built-in skills after init', () => {
+4 -3
View File
@@ -108,13 +108,14 @@ async function promptForRoleProfile(
log.info(` ${index + 1}. ${label}`);
});
const primaryAnswer = await askQuestion('Primary role (number): ').catch(() => {
const primaryAnswer = await askQuestion('Primary role (number or comma-separated numbers, primary first): ').catch(() => {
throw new Error(
'This team repo has several roles and there is no terminal to pick one. ' +
`Pass --role <id> (one of: ${listRoleIds(manifest).join(', ')}).`,
);
});
const [primaryIndex] = parseRoleSelection(primaryAnswer, manifest.roles.length);
const selectedIndexes = parseRoleSelection(primaryAnswer, manifest.roles.length);
const [primaryIndex, ...additionalIndexes] = selectedIndexes;
if (!primaryIndex) {
throw new NoRoleSelectedError('A primary role is required.');
}
@@ -123,7 +124,7 @@ async function promptForRoleProfile(
return {
primaryRole: primaryRole.id,
additionalRoles: [],
additionalRoles: additionalIndexes.map((index) => manifest.roles[index - 1].id),
resourceProfileVersion: manifest.version,
};
}