sync: update from private repo (dfadcd5f)
CI / build-and-test (push) Has been cancelled

This commit is contained in:
oss-sync
2026-06-23 06:38:48 +00:00
parent 6a2f2cc736
commit 29ccaf1e92
377 changed files with 31028 additions and 8994 deletions
+35 -14
View File
@@ -19,6 +19,25 @@ export function setSkillToolDeps(deps: SkillToolDeps | null): void {
_deps = deps;
}
/**
* Resolves the folder `(root, leaf)` that user-scope skills should read/write,
* mirroring AGENTS.md / memory (user-folder.ts folderTarget). When the job
* belongs to a (case) workspace, the worker wires `ctx.folderContext`
* (`{rootDir, leafId}`) → skills land in `data/spaces/{id}/skills/` and are
* shared with members. Without folderContext (gitea-issue / legacy / personal)
* we fall back to the caller's personal folder — byte-identical to before
* (the personal root is taken from the catalog so it matches getUserSkillDir).
*
* `system` scope is unaffected: that path uses catalog.getSystemDir() directly.
*/
function skillFolderTarget(ctx: ToolContext): { root: string; leaf: string } {
if (ctx.folderContext) {
return { root: ctx.folderContext.rootDir, leaf: ctx.folderContext.leafId };
}
const personalRoot = ctx.skillCatalog?.getUserRoot() ?? _deps?.userFolderRoot ?? './data/users';
return { root: personalRoot, leaf: ctx.userId ?? 'local' };
}
// ── Skill materialization ───────────────────────────────────────────────────
// When the agent ReadSkill's a directory-based skill, copy its files into the
// task workspace (`{workspace}/skills/{name}/`) so its scripts are usable in
@@ -151,7 +170,7 @@ function executeInstallSkill(
if (!VALID_SKILL_NAME.test(skillName)) {
return { output: `InstallSkill: invalid skill name "${skillName}". Only [a-z0-9_-] allowed.`, isError: true };
}
if (scope === 'system' && ctx.notesUserRole !== 'admin') {
if (scope === 'system' && ctx.userRole !== 'admin') {
return { output: 'InstallSkill: system-scope install requires admin role', isError: true };
}
@@ -160,10 +179,12 @@ function executeInstallSkill(
return { output: 'InstallSkill: skill catalog not available', isError: true };
}
const userId = ctx.userId ?? 'local';
// user-scope skills resolve to the job's folder (space → shared, else personal).
const { root: folderRoot, leaf: folderLeaf } = skillFolderTarget(ctx);
// Count check (user scope only)
// Count check (user scope only) — counts skills in the resolved folder.
if (scope === 'user') {
const userSkillCount = catalog.getForUser(userId).filter(s => s.source === 'user').length;
const userSkillCount = catalog.getForFolder(folderRoot, folderLeaf).filter(s => s.source === 'user').length;
if (userSkillCount >= 50) {
return { output: `InstallSkill: user skill limit reached (${userSkillCount}/50)`, isError: true };
}
@@ -205,7 +226,7 @@ function executeInstallSkill(
return { output: `InstallSkill: blocked by security scan:\n${details}`, isError: true };
}
const targetBase = scope === 'system' ? catalog.getSystemDir() : catalog.getUserSkillDir(userId);
const targetBase = scope === 'system' ? catalog.getSystemDir() : catalog.getSkillDirForFolder(folderRoot, folderLeaf);
const target = join(targetBase, skillName);
const tmpTarget = target + '.tmp-' + Date.now();
@@ -221,8 +242,8 @@ function executeInstallSkill(
return { output: `InstallSkill: install failed: ${e}`, isError: true };
}
if (scope === 'system') { catalog.refreshSystem(); } else { catalog.invalidate(userId); }
_deps?.auditLog?.('skill_installed', { skillName, scope, userId, format: 'directory', fileCount: stats.fileCount }, ctx.taskId ?? null);
if (scope === 'system') { catalog.refreshSystem(); } else { catalog.invalidateFolder(folderRoot, folderLeaf); }
_deps?.auditLog?.('skill_installed', { skillName, scope, userId, folderRoot, folderLeaf, format: 'directory', fileCount: stats.fileCount }, ctx.taskId ?? null);
const mediumFindings = findings.filter(f => f.severity === 'medium');
let msg = `InstallSkill: installed "${skillName}" to ${scope} scope (${stats.fileCount} files)`;
@@ -246,7 +267,7 @@ function executeInstallSkill(
return { output: `InstallSkill: blocked by security scan:\n${details}`, isError: true };
}
const targetDir = scope === 'system' ? catalog.getSystemDir() : catalog.getUserSkillDir(userId);
const targetDir = scope === 'system' ? catalog.getSystemDir() : catalog.getSkillDirForFolder(folderRoot, folderLeaf);
const skillDir = join(targetDir, skillName);
const tmpDir = join(targetDir, `.${skillName}.tmp.${Date.now()}`);
@@ -262,8 +283,8 @@ function executeInstallSkill(
return { output: `InstallSkill: write failed: ${(err as Error).message}`, isError: true };
}
if (scope === 'system') { catalog.refreshSystem(); } else { catalog.invalidate(userId); }
_deps?.auditLog?.('skill_installed', { skillName, scope, userId, scanSeverity: severity, findingsCount: findings.length }, ctx.taskId ?? null);
if (scope === 'system') { catalog.refreshSystem(); } else { catalog.invalidateFolder(folderRoot, folderLeaf); }
_deps?.auditLog?.('skill_installed', { skillName, scope, userId, folderRoot, folderLeaf, scanSeverity: severity, findingsCount: findings.length }, ctx.taskId ?? null);
const mediumFindings = findings.filter(f => f.severity === 'medium');
let msg = `InstallSkill: installed "${skillName}" to ${scope} scope`;
@@ -329,8 +350,8 @@ export function executeSkillTool(
if (name === 'ListSkills') {
const catalog = ctx.skillCatalog;
if (!catalog) return { output: 'Error: skill catalog not available', isError: true };
const userId = ctx.userId ?? 'local';
const entries = catalog.getForUser(userId);
const { root, leaf } = skillFolderTarget(ctx);
const entries = catalog.getForFolder(root, leaf);
if (entries.length === 0) return { output: 'No skills installed.', isError: false };
const lines = entries.map(e => {
const dirSuffix = 'dirPath' in e && e.dirPath ? ' (has scripts/)' : '';
@@ -351,10 +372,10 @@ export function executeSkillTool(
return { output: 'Error: skill catalog not available', isError: true };
}
const userId = ctx.userId ?? 'local';
const result = catalog.getSkillContent(skillName, userId);
const { root, leaf } = skillFolderTarget(ctx);
const result = catalog.getSkillContentForFolder(skillName, root, leaf);
if (result === null) {
const available = catalog.getForUser(userId).map(s => s.name).join(', ');
const available = catalog.getForFolder(root, leaf).map(s => s.name).join(', ');
return {
output: `Skill "${skillName}" not found. Available skills: ${available || '(none)'}`,
isError: true,