mirror of
https://github.com/Tencent/teamai-cli.git
synced 2026-10-02 03:14:40 +08:00
fix(pull): let no unusable namespace item replace the root one (#707)
A skill directory without SKILL.md replaced the root skill of its name: install overlaid it and removed the installed SKILL.md as the other version's leftover, while pull still counted the skill as synced. Such a directory is no longer a skill; pull names it and keeps delivering the root one. An agent file that does not parse delivers nothing, yet it still replaced the root agent, and cleanup removed the unchanged root copy because no active destination held that stem. The root agent now stays while its replacement cannot be read or parsed.
This commit is contained in:
+1
-1
@@ -13,7 +13,7 @@ All notable changes to this project will be documented in this file. See [standa
|
||||
### ✨ Features
|
||||
|
||||
- Env variables, hooks and MCP servers are scoped the way skills and agents are. `env/<ns>/env.yaml`, `hooks/<ns>/hooks.yaml` and `mcp/<ns>/mcp.yaml` reach only members whose role or directory's project lists `<ns>` under `resources.env`, `resources.hooks` or `resources.mcp`; the root files still reach everyone. A namespace entry replaces the root entry of the same variable `key`, hook `id` or server `name`, whole: an MCP override carries its own `command`, `args`, `env` and `tools:`, and one without `tools:` reaches every tool. When a namespace stops being active the next pull, `Already synced` included, restores the root entries it overrode and removes the ones only it had; `env.sh` is rewritten even when `env/env.yaml` is missing or declares nothing, and MCP `${VAR}` reads the same resolved variables. `teamai env add` and `teamai env remove` take `--role <ns>` / `--project <id>` to edit a namespace file (`--role` warns when no role or project declares the namespace; neither edits a file that does not parse, and `--project` changes nothing when the team repo cannot be refreshed), `teamai push` picks up a change to any `env/<ns>/env.yaml`, and `teamai remove mcp <name>` removes from the root file when it defines the name, otherwise from the one namespace file that does, asking for `--role` / `--project` only when several do, and removing nothing by a bare name the root file does not define while an MCP file does not parse; a flag that names a file that does not parse says so instead of reporting the name as not found. `teamai env list`, `teamai mcp list`, `teamai hooks list` and `teamai list <env|hooks|mcp> --source repo` show each entry's namespace and whether it overrides the root, and `teamai status` and `teamai doctor` count per namespace. A hooks or MCP file that cannot be resolved makes `teamai hooks inject` and `teamai mcp inject` exit 1 instead of reporting success, and hooks or model profiles that cannot be resolved fail `teamai doctor`'s `Team hooks can be resolved` or `Team model profiles can be resolved`, where `teamai status` points. A declared namespace matches its directory case-folded, as docs namespaces do, so `env/Checkout/` serves `env: [checkout]` on Linux too, and `--role` / `--project` write into that directory. So a checkout project can point `API_BASE` or a shared MCP server at its own backend under the same name (for [#707](https://github.com/Tencent/teamai-cli/issues/707)).
|
||||
- An item in an active namespace replaces the root item of the same name for skills, agents, rules and CLAUDE.md fragments too, so contradictory versions are no longer delivered side by side. An agent in `agents/<ns>/` replaces the root agent of the same stem instead of failing the pull, and deactivating the namespace brings the root agent back; a root file of the same stem no longer withdraws a placement record. `rules/<ns>/<name>.md` replaces `rules/<name>.md`, in Hermes' `SOUL.md` block too; deeper paths replace nothing. In the rule directories shared with a member's own rules (JoyCode, OMP, Pi, Copilot), the replaced root copy is removed while it is what teamai delivered, now or at the last pull, and an edited copy is kept and named on each pull. `claudemd/<ns>/<name>.md` replaces `claudemd/<name>.md` in the managed block. A root skill received through a tag is replaced by an active namespace skill of the same name; root skills are still not delivered by default in role or project mode, and among tag matches the root skill wins over one in an inactive namespace. Installing a skill removes the files that another team version of it has and the new one lacks, when they match that version byte for byte, so switching between versions leaves nothing of the other behind, while a file you added or edited stays; one at a path another version has is named on each pull. `teamai push` writes an edit of a replacing item back to its namespace and never to the root, and the skills push scan covers project namespaces as well as role ones. `teamai recall` indexes the skills and rules you receive rather than every one in the repo, so a replaced root rule or a rule of an inactive namespace is not returned. Two namespace rules or CLAUDE.md files of one name are both delivered, since each keeps its own place. `teamai doctor` lists every replacement as a note, in `--json` under `notes`; without roles or projects nothing changes, and the notes list each name the team repo defines more than once. Keep content a project may override at the root: a namespace item never gives way, so a rule in `rules/common/` is delivered beside a project's rule of the same name (for [#707](https://github.com/Tencent/teamai-cli/issues/707)).
|
||||
- An item in an active namespace replaces the root item of the same name for skills, agents, rules and CLAUDE.md fragments too, so contradictory versions are no longer delivered side by side. An agent in `agents/<ns>/` replaces the root agent of the same stem instead of failing the pull, and deactivating the namespace brings the root agent back; a root file of the same stem no longer withdraws a placement record. `rules/<ns>/<name>.md` replaces `rules/<name>.md`, in Hermes' `SOUL.md` block too; deeper paths replace nothing. In the rule directories shared with a member's own rules (JoyCode, OMP, Pi, Copilot), the replaced root copy is removed while it is what teamai delivered, now or at the last pull, and an edited copy is kept and named on each pull. `claudemd/<ns>/<name>.md` replaces `claudemd/<name>.md` in the managed block. A root skill received through a tag is replaced by an active namespace skill of the same name; root skills are still not delivered by default in role or project mode, and among tag matches the root skill wins over one in an inactive namespace. Installing a skill removes the files that another team version of it has and the new one lacks, when they match that version byte for byte, so switching between versions leaves nothing of the other behind, while a file you added or edited stays; one at a path another version has is named on each pull. `teamai push` writes an edit of a replacing item back to its namespace and never to the root, and the skills push scan covers project namespaces as well as role ones. `teamai recall` indexes the skills and rules you receive rather than every one in the repo, so a replaced root rule or a rule of an inactive namespace is not returned. Two namespace rules or CLAUDE.md files of one name are both delivered, since each keeps its own place. A replacement that cannot be used replaces nothing: a skill directory without `SKILL.md` is not delivered (pull names it), and while an agent file does not parse the agent it would replace stays installed. `teamai doctor` lists every replacement as a note, in `--json` under `notes`; without roles or projects nothing changes, and the notes list each name the team repo defines more than once. Keep content a project may override at the root: a namespace item never gives way, so a rule in `rules/common/` is delivered beside a project's rule of the same name (for [#707](https://github.com/Tencent/teamai-cli/issues/707)).
|
||||
- `docs/<ns>/` can be scoped: once any role or project lists `<ns>` under `resources.docs`, those docs reach only the members who have that namespace active. A `docs/<dir>/` that no role or project lists stays shared, so existing subdirectories keep reaching everyone. When the namespace stops being active, the next pull removes the local copies that still match the team file byte for byte, now or in an earlier team commit, and keeps an edited one, naming it. The docs mirror of `sharing.docs.localDir` copies only the docs a member receives and never removes such an edited copy. `teamai recall` and `teamai doctor`'s `Team docs delivered` follow the same filter, and doctor does not report a kept copy as stale. `team-codebase` cannot be a docs namespace, since `docs/team-codebase/` is the legacy codebase output; a manifest that declares it fails to load (for [#707](https://github.com/Tencent/teamai-cli/issues/707)).
|
||||
- Team model profiles can be scoped: `models/<ns>/models.yaml`, declared under `resources.models`, replaces the root profile of the same `id` for members with that namespace active. Agents switched to `team:<id>` follow the override on the next pull and return to the root profile when it deactivates; a profile that existed only in a namespace you left keeps your agent settings, and pull says it `is no longer active in your namespaces`. A stored team API key is bound to the profile `id` and the origin (scheme, host, port) of its `base_url`, so an override never sends your key to another gateway: when a profile moves to an origin you have no key for, pull leaves the agents switched to it alone and prints ``Run `teamai models switch team:<id>` to set a key for it.``, and the key for the first gateway is kept for when you leave the namespace. The same applies when the team moves the root profile to another origin, with or without roles and projects, so each member runs the switch once per new gateway. Model profiles exist only in the 0.26.0 betas, so no stable release is affected. A key stored by a beta is bound once, at the first command or pull that reads it: to the gateway TeamAI last wrote it into for your agents, or, if no agent was switched to that profile, to the root profile's current gateway. If the team moved the root profile to another origin since your agent was switched, pull leaves that agent alone and asks for the switch, rather than sending the old key to the new host. `teamai models list` shows the file each team profile comes from, and `teamai push` refuses any invalid models file (for [#707](https://github.com/Tencent/teamai-cli/issues/707)).
|
||||
- Built-in skill content ships inside the npm package and is printed by the installed CLI: `teamai skill get <core|setup|wiki|share> [--full] [--all]`, `teamai skill path <name>` for the directory holding a skill's scripts, and `teamai skill list --json` for the catalog. Agents receive one file, `skills/teamai/SKILL.md`, a discovery stub that points at those commands, so what an agent reads always matches the CLI version it is running. `teamai pull` removes the `team-wiki-codebase`, `teamai-share-learnings` and `teamai/references/*.md` trees earlier releases copied into every agent directory, removing only files whose content a release shipped (an edited file, or a member's own skill under an old name, stays), archiving each removed file under `~/.teamai/removed-skills/<run>/…` first, and keeping any directory that holds a member's own file; `teamai uninstall` removes only the packaged files from CLI-owned skill directories by the same rule. `share` is served only while recall is on and the team source is writable (not a read-only HTTP one), and the end-of-session share reminder is withheld until then too. The served workflows are English; learning and knowledge-base documents are still written in Simplified Chinese, and an existing knowledge base keeps its file names and headings. The legacy names still resolve as aliases (for [#678](https://github.com/Tencent/teamai-cli/issues/678), [#730](https://github.com/Tencent/teamai-cli/issues/730)).
|
||||
|
||||
@@ -279,6 +279,13 @@ or edited stays, because push never counts such extras as changes and they may
|
||||
never have been pushed, and one at a path another version has is named on each
|
||||
pull.
|
||||
|
||||
An item that cannot be used replaces nothing. A skill directory without
|
||||
`SKILL.md` is not a skill: it is left out of the desired set, so the root skill
|
||||
of its name is still delivered and its installed `SKILL.md` is never removed as
|
||||
a leftover, and pull names the directory once a run. While a namespace agent
|
||||
file cannot be read or parsed it delivers nothing, and cleanup keeps the root
|
||||
agent it would replace.
|
||||
|
||||
Overridable shared content belongs at the root, not in a namespace every role
|
||||
activates (`common/`): a root item gives way to an active namespace, a namespace
|
||||
item never does. `rules/code-style.md` is replaced by `rules/checkout/code-style.md`
|
||||
|
||||
+1
-1
@@ -584,7 +584,7 @@ With role-based skills enabled, `pull`'s skill sync source becomes the contents
|
||||
- A rule replaces the root rule of the same first-level file name: `rules/<ns>/<name>.md` replaces `rules/<name>.md`, in Hermes' `SOUL.md` block too. Deeper paths such as `rules/<ns>/<dir>/<name>.md` replace nothing, and neither does a namespace rule your tag subscriptions leave out. In rule directories you share with rules of your own (JoyCode, OMP, Pi, Copilot), the replaced root rule's copy is removed only while it is what teamai delivered (the current root rule, or the one of your last pull); an edited copy stays, and each pull names it, since the tool loads it beside the namespace rule.
|
||||
- A `claudemd/<ns>/<name>.md` file replaces `claudemd/<name>.md` in the managed block.
|
||||
|
||||
When the namespace stops being active, the next pull delivers the root item again. If two active namespaces define the same skill or agent name, they compete for one installed file, so pull reports an error that names both files, does not update that type in that run, and keeps what is installed (for skills, recall keeps the ones it had indexed too); the other resource types still sync. Two active namespaces with the same rule or shared-instructions name are both delivered, because each keeps its own place (`rules/<ns>/` locally, its own section of the block); only the root one gives way. `push` writes an edit of a replaced item back to its namespace, never to the root, and recall indexes the skills and rules you receive rather than every one in the repo. `teamai doctor` lists each replacement as a note. Without roles or projects nothing changes: every namespace is delivered beside the root, and `doctor` lists each name the team repo defines more than once.
|
||||
When the namespace stops being active, the next pull delivers the root item again. If two active namespaces define the same skill or agent name, they compete for one installed file, so pull reports an error that names both files, does not update that type in that run, and keeps what is installed (for skills, recall keeps the ones it had indexed too); the other resource types still sync. Two active namespaces with the same rule or shared-instructions name are both delivered, because each keeps its own place (`rules/<ns>/` locally, its own section of the block); only the root one gives way. `push` writes an edit of a replaced item back to its namespace, never to the root, and recall indexes the skills and rules you receive rather than every one in the repo. A replacement that cannot be used replaces nothing: a skill directory without `SKILL.md` is not delivered and pull names it, and while an agent file does not parse the agent it would replace stays installed. `teamai doctor` lists each replacement as a note. Without roles or projects nothing changes: every namespace is delivered beside the root, and `doctor` lists each name the team repo defines more than once.
|
||||
|
||||
Put shared content that a project may need to override at the root, not in a namespace every role activates. A root item gives way to an active namespace; a namespace item never does. For example, keep the company's `rules/code-style.md` at the root, and a checkout project that needs different conventions adds `rules/checkout/code-style.md`. Members with `checkout` active get the project's version, and everyone else keeps the shared one. Had the shared rule lived in `rules/common/code-style.md`, a checkout member would receive both.
|
||||
|
||||
|
||||
@@ -539,7 +539,7 @@ teamai pull --dry-run # 试运行,不实际修改
|
||||
- rule 按第一层文件名替换:`rules/<ns>/<name>.md` 替换 `rules/<name>.md`,Hermes 的 `SOUL.md` 区块同样如此。更深的路径(如 `rules/<ns>/<dir>/<name>.md`)不替换任何文件,被你的标签订阅排除的 namespace rule 也不替换。在与你自己的 rule 共用的目录中(JoyCode、OMP、Pi、Copilot),被替换的根 rule 副本只在仍是 teamai 所下发的内容(当前的根 rule,或你上次 pull 时的版本)时删除;你改过的副本会保留,且每次 pull 都会点名它,因为工具会把它与 namespace rule 一起加载。
|
||||
- `claudemd/<ns>/<name>.md` 在托管区块中替换 `claudemd/<name>.md`。
|
||||
|
||||
该 namespace 不再活跃后,下一次 pull 会重新下发根目录条目。两个活跃 namespace 定义同名 skill 或 agent 时,它们会争用同一个安装文件,因此 pull 会报错并列出两个文件,本次运行不更新该类型,已安装的内容保持不变(skills 在 recall 中已有的索引也保持不变);其他资源类型照常同步。两个活跃 namespace 定义同名 rule 或共享指令时,两者都会下发,因为它们各有自己的位置(本地的 `rules/<ns>/`、区块中各自的一段);只有根目录的那一份会让位。`push` 会把被替换条目的修改写回其 namespace,而不会写到根目录;recall 只索引你实际收到的 skills 和 rules,而不是仓库中的全部内容。`teamai doctor` 会以提示的形式列出每一处替换。未配置角色或项目时行为不变:所有 namespace 与根目录并列下发,`doctor` 会列出团队仓库中重复定义的每个名称。
|
||||
该 namespace 不再活跃后,下一次 pull 会重新下发根目录条目。两个活跃 namespace 定义同名 skill 或 agent 时,它们会争用同一个安装文件,因此 pull 会报错并列出两个文件,本次运行不更新该类型,已安装的内容保持不变(skills 在 recall 中已有的索引也保持不变);其他资源类型照常同步。两个活跃 namespace 定义同名 rule 或共享指令时,两者都会下发,因为它们各有自己的位置(本地的 `rules/<ns>/`、区块中各自的一段);只有根目录的那一份会让位。`push` 会把被替换条目的修改写回其 namespace,而不会写到根目录;recall 只索引你实际收到的 skills 和 rules,而不是仓库中的全部内容。无法使用的替换项不会替换任何内容:没有 `SKILL.md` 的 skill 目录不会下发,pull 会点名提示;agent 文件无法解析时,它原本要替换的 agent 保持安装。`teamai doctor` 会以提示的形式列出每一处替换。未配置角色或项目时行为不变:所有 namespace 与根目录并列下发,`doctor` 会列出团队仓库中重复定义的每个名称。
|
||||
|
||||
项目可能需要覆盖的共享内容应放在根目录,而不是放在每个角色都会激活的 namespace 中:根目录条目会让位给活跃的 namespace,namespace 条目则不会。例如,公司的 `rules/code-style.md` 放在根目录;需要不同规范的 checkout 项目添加 `rules/checkout/code-style.md`。激活了 `checkout` 的成员拿到项目版本,其他人仍使用共享版本。如果共享规则放在 `rules/common/code-style.md`,checkout 成员就会同时收到两份。
|
||||
|
||||
|
||||
@@ -113,7 +113,9 @@ a namespace every role activates: `rules/code-style.md` is replaced by
|
||||
`rules/common/code-style.md` would reach them alongside it. The same skill or
|
||||
agent name in two namespaces one member has active is an error naming both
|
||||
files; two namespace rules or claudemd files of one name are both delivered.
|
||||
`teamai doctor` lists each replacement as a note. Teams without roles or
|
||||
A replacement must be usable to replace anything: a skill directory needs its
|
||||
`SKILL.md` (pull names one without it), and while an agent file does not parse
|
||||
the root agent stays installed. `teamai doctor` lists each replacement as a note. Teams without roles or
|
||||
projects are unaffected.
|
||||
|
||||
Docs have no override. A top-level `docs/<ns>/` that any role or project lists
|
||||
|
||||
@@ -180,6 +180,20 @@ describe('pull: an active namespace item replaces the root item of the same name
|
||||
expect(index.entries.map((entry) => entry.filename)).toContain('lint.md');
|
||||
});
|
||||
|
||||
// A replacement that does not parse delivers nothing, so it must not take
|
||||
// the root agent it would replace away with it.
|
||||
it('keeps the root agent while the namespace agent replacing it does not parse', async () => {
|
||||
await team('agents/reviewer.yaml', 'name: reviewer\ndescription: Shared\ninstructions: Review for everyone.\n');
|
||||
as(['devops']);
|
||||
await pull({});
|
||||
expect(await read('.claude/agents/reviewer.md')).toContain('Review for everyone.');
|
||||
|
||||
await team('agents/devops/reviewer.yaml', 'name: reviewer\ndescription: [\n');
|
||||
await pull({ force: true });
|
||||
|
||||
expect(await read('.claude/agents/reviewer.md')).toContain('Review for everyone.');
|
||||
});
|
||||
|
||||
it('names the one namespace and both files when an agent is defined twice inside it', async () => {
|
||||
await team('agents/frontend/reviewer.yaml', 'name: reviewer\ndescription: Front\ninstructions: Review the front end.\n');
|
||||
await team('agents/frontend/reviewer.md', '---\nname: reviewer\ndescription: Legacy\n---\nReview, the old way.\n');
|
||||
@@ -406,6 +420,21 @@ describe('pull: an active namespace item replaces the root item of the same name
|
||||
));
|
||||
});
|
||||
|
||||
// A directory without SKILL.md is not a skill: it must neither replace the
|
||||
// root skill nor strip the installed one of its SKILL.md.
|
||||
it('keeps delivering the root skill while the namespace directory of its name has no SKILL.md', async () => {
|
||||
as(['devops'], { subscribedTags: ['ui'] });
|
||||
await pull({});
|
||||
expect(await read('.claude/skills/review/SKILL.md')).toContain('Shared review');
|
||||
|
||||
await team('skills/devops/review/notes.md', 'draft notes\n');
|
||||
await pull({ force: true });
|
||||
|
||||
expect(await read('.claude/skills/review/SKILL.md')).toContain('Shared review');
|
||||
expect(await exists('.claude/skills/review/notes.md')).toBe(false);
|
||||
expect(logged('warn', /skills\/devops\/review has no SKILL\.md/)).toBe(true);
|
||||
});
|
||||
|
||||
it('indexes for recall the skills pull delivers, not every skill in the repo', async () => {
|
||||
await team('skills/devops/deploy/SKILL.md', skillMd('deploy', 'Deploy things'));
|
||||
|
||||
|
||||
@@ -803,6 +803,12 @@ export class AgentsHandler extends ResourceHandler {
|
||||
(item) => !kept.has(item.relativePath) && !BUILTIN_AGENT_NAMES.has(item.name),
|
||||
);
|
||||
if (inactive.length === 0) return;
|
||||
// A replacement that cannot be read or parsed delivers nothing, so the
|
||||
// agent it replaces stays until the team repo fixes it.
|
||||
const unusable = new Set<string>();
|
||||
for (const item of active) {
|
||||
if (!await this.parsesAsAgent(item)) unusable.add(item.name);
|
||||
}
|
||||
|
||||
for (const { tool, dir: destDir } of await this.agentToolDirs(teamConfig, localConfig)) {
|
||||
const activeDestinations = new Set<string>();
|
||||
@@ -811,6 +817,7 @@ export class AgentsHandler extends ResourceHandler {
|
||||
if (rendered) activeDestinations.add(`${item.name}${rendered.ext}`);
|
||||
}
|
||||
for (const item of inactive) {
|
||||
if (unusable.has(item.name)) continue;
|
||||
const expected = await this.renderedForTool(item, tool);
|
||||
if (!expected || activeDestinations.has(`${item.name}${expected.ext}`)) continue;
|
||||
const deployed = path.join(destDir, `${item.name}${expected.ext}`);
|
||||
@@ -894,6 +901,13 @@ export class AgentsHandler extends ResourceHandler {
|
||||
* is not a target (legacy `.md` only reaches LEGACY_MD_TOOLS, a YAML spec
|
||||
* honours `targets`, an unparsable spec is skipped like pull skips it).
|
||||
*/
|
||||
/** Whether `item`'s team file can be read and, for a YAML spec, parses. */
|
||||
private async parsesAsAgent(item: AgentResourceItem): Promise<boolean> {
|
||||
const content = await readFileSafe(item.sourcePath);
|
||||
if (content === null) return false;
|
||||
return isLegacyAgent(item) || parseAgentYaml(content, `${item.name}.yaml`).ok;
|
||||
}
|
||||
|
||||
private async renderedForTool(item: AgentResourceItem, tool: ToolName): Promise<RenderResult | null> {
|
||||
const content = await readFileSafe(item.sourcePath);
|
||||
if (content === null) return null;
|
||||
|
||||
@@ -18,6 +18,7 @@ import type { LocalConfig, ResourceItem, TeamaiConfig } from '../types.js';
|
||||
import { listDirs, listFiles, pathExists, readFileSafe } from '../utils/fs.js';
|
||||
import type { BuildIndexOptions, IndexedSkills } from '../utils/search-index.js';
|
||||
import { filterByTags, loadTagsConfig } from '../utils/tags.js';
|
||||
import { warnOnce } from '../utils/warn-once.js';
|
||||
import { resolveAgentsForDirectory } from './agents.js';
|
||||
import { resolveDocsForDirectory } from './docs.js';
|
||||
import { getHandler } from './index.js';
|
||||
@@ -177,6 +178,23 @@ export function filterAgentsByNamespaces(
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* The items that are skills: a directory without SKILL.md is not one, so it
|
||||
* neither replaces the root skill of its name nor is installed over it, which
|
||||
* would strip the installed copy of its SKILL.md. Each one is named once a run.
|
||||
*/
|
||||
async function withSkillMd(items: ResourceItem[]): Promise<ResourceItem[]> {
|
||||
const skills: ResourceItem[] = [];
|
||||
for (const item of items) {
|
||||
if (await pathExists(path.join(item.sourcePath, 'SKILL.md'))) {
|
||||
skills.push(item);
|
||||
} else {
|
||||
warnOnce(`${item.relativePath} has no SKILL.md, so it is not delivered as a skill. Add SKILL.md to it in the team repo, or remove it.`);
|
||||
}
|
||||
}
|
||||
return skills;
|
||||
}
|
||||
|
||||
export async function scanRoleAwareSkills(
|
||||
localConfig: LocalConfig,
|
||||
namespaces: ResourceNamespaces,
|
||||
@@ -197,7 +215,7 @@ export async function scanRoleAwareSkills(
|
||||
}
|
||||
}
|
||||
|
||||
const resolution = resolveNamespacedItems(items.map(itemCandidate), namespaces.skills);
|
||||
const resolution = resolveNamespacedItems((await withSkillMd(items)).map(itemCandidate), namespaces.skills);
|
||||
if (resolution.kind === 'conflict') return { kind: 'conflict', type: 'skill', conflict: resolution };
|
||||
return { kind: 'resolved', items: resolution.items.map((item) => item.value) };
|
||||
}
|
||||
@@ -237,10 +255,10 @@ export async function resolveDesiredSkills(
|
||||
if (scanned.kind === 'conflict') return scanned;
|
||||
directoryItems = scanned.items;
|
||||
} else {
|
||||
directoryItems = await handler.scanTeamForPull(teamConfig, localConfig);
|
||||
directoryItems = await withSkillMd(await handler.scanTeamForPull(teamConfig, localConfig));
|
||||
}
|
||||
|
||||
const teamItems = await handler.scanTeamForPull(teamConfig, localConfig);
|
||||
const teamItems = await withSkillMd(await handler.scanTeamForPull(teamConfig, localConfig));
|
||||
|
||||
// Tag channel: only augment when subscriptions are actually active
|
||||
const hasActiveTagSubscriptions = tagsConfig != null
|
||||
|
||||
Reference in New Issue
Block a user