duclaw-cli 1.9.7 → 1.9.9

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/dist/bundle.js CHANGED
@@ -30242,7 +30242,7 @@ function printHelp() {
30242
30242
  `);
30243
30243
  }
30244
30244
  function printVersion() {
30245
- console.log(`duclaw-cli v${true ? "1.9.7" : "unknown"}`);
30245
+ console.log(`duclaw-cli v${true ? "1.9.9" : "unknown"}`);
30246
30246
  }
30247
30247
  function getDuclawTemplate() {
30248
30248
  return {
@@ -41782,9 +41782,9 @@ var imageUnderstand = {
41782
41782
  };
41783
41783
 
41784
41784
  // src/skill/SkillRegistry.ts
41785
- var import_fs11 = require("fs");
41785
+ var import_fs13 = require("fs");
41786
41786
  var import_os3 = require("os");
41787
- var import_path16 = __toESM(require("path"));
41787
+ var import_path18 = __toESM(require("path"));
41788
41788
 
41789
41789
  // src/runtime/paths.ts
41790
41790
  var import_fs10 = require("fs");
@@ -41813,33 +41813,608 @@ var resolveCoreRoot = () => {
41813
41813
  return candidate;
41814
41814
  }
41815
41815
  }
41816
- return candidates[0];
41816
+ return candidates[0];
41817
+ };
41818
+ var resolveWebDistRoot = () => {
41819
+ const coreRoot = resolveCoreRoot();
41820
+ const candidates = [
41821
+ import_path15.default.join(coreRoot, "dist", "web"),
41822
+ import_path15.default.join(coreRoot, "web", "dist")
41823
+ ];
41824
+ for (const candidate of candidates) {
41825
+ if ((0, import_fs10.existsSync)(import_path15.default.join(candidate, "index.html"))) {
41826
+ return candidate;
41827
+ }
41828
+ }
41829
+ return candidates[0];
41830
+ };
41831
+
41832
+ // src/department/Department.ts
41833
+ var import_path16 = __toESM(require("path"));
41834
+ var import_fs11 = require("fs");
41835
+ var legacyMigrationChecked = false;
41836
+ var getDepartmentBaseDir = () => {
41837
+ return import_path16.default.join(getDuclawHomeDir(), "department");
41838
+ };
41839
+ var getLegacyTeamBaseDir = () => {
41840
+ return import_path16.default.join(getDuclawHomeDir(), "team");
41841
+ };
41842
+ var getDepartmentWorkSpaceDir = (departmentName) => {
41843
+ return import_path16.default.join(getDepartmentBaseDir(), "workspace", departmentName);
41844
+ };
41845
+ var getLegacyTeamWorkSpaceDir = (teamName) => {
41846
+ return import_path16.default.join(getLegacyTeamBaseDir(), "workspace", teamName);
41847
+ };
41848
+ var getDepartmentJsonPath = (departmentName) => {
41849
+ return import_path16.default.join(getDepartmentWorkSpaceDir(departmentName), "department.json");
41850
+ };
41851
+ var getLegacyTeamJsonPath = (teamName) => {
41852
+ return import_path16.default.join(getLegacyTeamWorkSpaceDir(teamName), "team.json");
41853
+ };
41854
+ var mapLegacyRole = (role) => {
41855
+ return role === "team_manager" ? "department_head" : "executor";
41856
+ };
41857
+ var mapLegacyDepartment = (legacy) => {
41858
+ const departmentMembers = (legacy.teamMembers ?? []).map((member) => ({
41859
+ id: member.id,
41860
+ name: member.name,
41861
+ departmentId: legacy.id,
41862
+ mailBoxId: member.mailBoxId,
41863
+ workspaceId: member.workspaceId,
41864
+ role: mapLegacyRole(member.role),
41865
+ focusOn: member.focusOn
41866
+ }));
41867
+ const headMemberId = legacy.managerMemberId ?? departmentMembers.find((member) => member.role === "department_head")?.id;
41868
+ return {
41869
+ id: legacy.id,
41870
+ name: legacy.name,
41871
+ sourceGoalId: legacy.goalId,
41872
+ charter: legacy.goalId ? `Legacy department migrated from team goal ${legacy.goalId}.` : "Legacy department migrated from team data.",
41873
+ workpath: legacy.workpath,
41874
+ headMemberId,
41875
+ departmentMembers
41876
+ };
41877
+ };
41878
+ var migrateLegacyTeamsToDepartments = () => {
41879
+ if (legacyMigrationChecked) return;
41880
+ legacyMigrationChecked = true;
41881
+ const legacyWorkspaceDir = import_path16.default.join(getLegacyTeamBaseDir(), "workspace");
41882
+ if (!(0, import_fs11.existsSync)(legacyWorkspaceDir)) return;
41883
+ for (const legacyName of (0, import_fs11.readdirSync)(legacyWorkspaceDir)) {
41884
+ const legacyJsonPath = getLegacyTeamJsonPath(legacyName);
41885
+ if (!(0, import_fs11.existsSync)(legacyJsonPath)) continue;
41886
+ const departmentJsonPath = getDepartmentJsonPath(legacyName);
41887
+ if ((0, import_fs11.existsSync)(departmentJsonPath)) continue;
41888
+ try {
41889
+ const legacy = JSON.parse((0, import_fs11.readFileSync)(legacyJsonPath, "utf-8"));
41890
+ const department = mapLegacyDepartment(legacy);
41891
+ (0, import_fs11.mkdirSync)(getDepartmentWorkSpaceDir(department.name), { recursive: true });
41892
+ (0, import_fs11.writeFileSync)(departmentJsonPath, JSON.stringify(department, null, " "), "utf-8");
41893
+ } catch (err) {
41894
+ console.warn(`[department] Failed to migrate legacy team ${legacyName}: ${err.message}`);
41895
+ }
41896
+ }
41897
+ };
41898
+ var createDepartment = (departmentDefinition) => {
41899
+ if (!departmentDefinition) throw new Error(`[createDepartment] departmentDefinition\u4E0D\u80FD\u4E3A\u7A7A`);
41900
+ const departmentPath = getDepartmentWorkSpaceDir(departmentDefinition.name);
41901
+ (0, import_fs11.mkdirSync)(departmentPath, { recursive: true });
41902
+ (0, import_fs11.writeFileSync)(getDepartmentJsonPath(departmentDefinition.name), JSON.stringify(departmentDefinition, null, " "), "utf-8");
41903
+ return departmentDefinition;
41904
+ };
41905
+ var getDepartment = (name) => {
41906
+ migrateLegacyTeamsToDepartments();
41907
+ const departmentJsonPath = getDepartmentJsonPath(name);
41908
+ if (!(0, import_fs11.existsSync)(departmentJsonPath)) return null;
41909
+ const text2 = (0, import_fs11.readFileSync)(departmentJsonPath, "utf-8");
41910
+ return JSON.parse(text2);
41911
+ };
41912
+ var listDepartments = () => {
41913
+ migrateLegacyTeamsToDepartments();
41914
+ const workspaceDir = import_path16.default.join(getDepartmentBaseDir(), "workspace");
41915
+ if (!(0, import_fs11.existsSync)(workspaceDir)) return [];
41916
+ const departments = [];
41917
+ for (const departmentName of (0, import_fs11.readdirSync)(workspaceDir)) {
41918
+ const department = getDepartment(departmentName);
41919
+ if (department) departments.push(department);
41920
+ }
41921
+ return departments;
41922
+ };
41923
+ var getDepartmentById = (id) => {
41924
+ const department = listDepartments().find((item) => item.id === id);
41925
+ return department ?? null;
41926
+ };
41927
+ var deleteDepartment = (name) => {
41928
+ if (!(0, import_fs11.existsSync)(getDepartmentJsonPath(name))) {
41929
+ throw new Error(`[deleteDepartment] \u4E0D\u5B58\u5728\u5BF9\u5E94\u7684\u90E8\u95E8 ${name} \u7684 department.json \u6587\u4EF6`);
41930
+ }
41931
+ (0, import_fs11.rmSync)(getDepartmentJsonPath(name));
41932
+ };
41933
+
41934
+ // src/department/learning.ts
41935
+ var import_node_fs4 = require("node:fs");
41936
+ var import_node_path13 = __toESM(require("node:path"));
41937
+ var import_node_crypto4 = require("node:crypto");
41938
+
41939
+ // src/department/DepartmentMember.ts
41940
+ var import_fs12 = require("fs");
41941
+ var import_path17 = __toESM(require("path"));
41942
+
41943
+ // src/department/workspace/workspace.ts
41944
+ var db = createSqliteDB();
41945
+ var getWorkspaceId = (departmentName, memberName) => {
41946
+ return `${departmentName}::${memberName}`;
41947
+ };
41948
+ var createWorkspace = (workspace) => {
41949
+ const stmt = db.prepare(`INSERT INTO workspace (id, team_name, teammate_name, team_workpath) VALUES (?, ?, ?, ?) `);
41950
+ stmt.run(
41951
+ workspace.id,
41952
+ workspace.departmentName,
41953
+ workspace.memberName,
41954
+ workspace.departmentWorkPath
41955
+ );
41956
+ };
41957
+ var deleteWorkspace = (workspaceId) => {
41958
+ const stmt = db.prepare(`delete from workspace where id = ?`);
41959
+ stmt.run(workspaceId);
41960
+ };
41961
+
41962
+ // src/department/DepartmentMember.ts
41963
+ var createDepartmentMember = (departmentMemberDefinition) => {
41964
+ if (!departmentMemberDefinition) throw new Error(`[createDepartmentMember] departmentMemberDefinition\u4E0D\u80FD\u4E3A\u7A7A`);
41965
+ const { name, departmentId, workspaceId } = departmentMemberDefinition;
41966
+ const department = getDepartmentById(departmentId);
41967
+ if (!department) throw new Error(`[createDepartmentMember] \u627E\u4E0D\u5230\u5BF9\u5E94\u7684 department: ${departmentId}`);
41968
+ const memberPath = import_path17.default.join(getDepartmentWorkSpaceDir(department.name), name);
41969
+ (0, import_fs12.mkdirSync)(memberPath, { recursive: true });
41970
+ const workspace = {
41971
+ id: getWorkspaceId(department.name, departmentMemberDefinition.name),
41972
+ departmentName: department.name,
41973
+ memberName: name,
41974
+ departmentWorkPath: memberPath
41975
+ };
41976
+ createWorkspace(workspace);
41977
+ if (!department.departmentMembers) {
41978
+ department.departmentMembers = [];
41979
+ }
41980
+ if (department.departmentMembers.some((member) => member.id === departmentMemberDefinition.id || member.name === departmentMemberDefinition.name)) {
41981
+ throw new Error(`[createDepartmentMember] \u90E8\u95E8 ${department.name} \u5DF2\u5B58\u5728\u540C\u540D\u6216\u540C id \u6210\u5458: ${departmentMemberDefinition.name}/${departmentMemberDefinition.id}`);
41982
+ }
41983
+ if (departmentMemberDefinition.role === "department_head") {
41984
+ const existingHead = department.headMemberId ? department.departmentMembers.find((member) => member.id === department.headMemberId) : department.departmentMembers.find((member) => member.role === "department_head");
41985
+ if (existingHead) {
41986
+ throw new Error(`[createDepartmentMember] \u90E8\u95E8 ${department.name} \u5DF2\u5B58\u5728 Department Head: ${existingHead.name}`);
41987
+ }
41988
+ department.headMemberId = departmentMemberDefinition.id;
41989
+ }
41990
+ department.departmentMembers.push(departmentMemberDefinition);
41991
+ (0, import_fs12.writeFileSync)(getDepartmentJsonPath(department.name), JSON.stringify(department, null, " "), "utf-8");
41992
+ return departmentMemberDefinition;
41993
+ };
41994
+ var listDepartmentMembers = (departmentName) => {
41995
+ const departmentJsonPath = getDepartmentJsonPath(departmentName);
41996
+ if (!(0, import_fs12.existsSync)(departmentJsonPath)) return [];
41997
+ const text2 = (0, import_fs12.readFileSync)(departmentJsonPath, "utf-8");
41998
+ const department = JSON.parse(text2);
41999
+ if (!department) throw new Error(`[listDepartmentMembers] \u627E\u4E0D\u5230\u5BF9\u5E94\u7684 department: ${departmentName}`);
42000
+ return department.departmentMembers ?? [];
42001
+ };
42002
+ var getDepartmentMember = (departmentName, departmentMemberId) => {
42003
+ const members = listDepartmentMembers(departmentName);
42004
+ return members.find((member) => member.id === departmentMemberId) || null;
42005
+ };
42006
+ var getDepartmentMemberByName = (departmentName, departmentMemberName) => {
42007
+ const members = listDepartmentMembers(departmentName);
42008
+ return members.find((member) => member.name === departmentMemberName) || null;
42009
+ };
42010
+ var getDepartmentMemberByMailboxId = (mailboxId) => {
42011
+ const [departmentName, memberName] = mailboxId.split("::");
42012
+ if (!departmentName || !memberName) return null;
42013
+ return getDepartmentMemberByName(departmentName, memberName);
42014
+ };
42015
+ var getDepartmentMemberById = (memberId) => {
42016
+ for (const department of listDepartments()) {
42017
+ const targetMember = department.departmentMembers.find((member) => member.id === memberId);
42018
+ if (targetMember) return targetMember;
42019
+ }
42020
+ return null;
42021
+ };
42022
+ var deleteDepartmentMemberById = (departmentName, memberId) => {
42023
+ const department = getDepartment(departmentName);
42024
+ if (!department) throw new Error(`[deleteDepartmentMemberById] \u627E\u4E0D\u5230\u5BF9\u5E94\u7684 department: ${departmentName}`);
42025
+ const memberIdx = department.departmentMembers.findIndex((member) => member.id === memberId);
42026
+ if (memberIdx === -1) {
42027
+ throw new Error(`[deleteDepartmentMemberById] \u627E\u4E0D\u5230\u5BF9\u5E94 id \u7684 department member: ${memberId}`);
42028
+ }
42029
+ const workspaceId = department.departmentMembers[memberIdx].workspaceId;
42030
+ if (department.headMemberId === memberId) {
42031
+ delete department.headMemberId;
42032
+ }
42033
+ department.departmentMembers = department.departmentMembers.filter((_, index) => index !== memberIdx);
42034
+ (0, import_fs12.writeFileSync)(getDepartmentJsonPath(departmentName), JSON.stringify(department, null, " "), "utf-8");
42035
+ deleteWorkspace(workspaceId);
42036
+ };
42037
+
42038
+ // src/skill/SkillValidator.ts
42039
+ var import_node_fs3 = require("node:fs");
42040
+ var import_node_path12 = __toESM(require("node:path"));
42041
+ var SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
42042
+ var REQUIRED_SECTIONS = [
42043
+ /(^|\n)##\s+(when to use|何时使用|trigger|triggers)\b/i,
42044
+ /(^|\n)##\s+(steps|workflow|procedure|执行步骤|工作流)\b/i
42045
+ ];
42046
+ var stripYamlQuotes = (value) => {
42047
+ const trimmed = value.trim();
42048
+ if (trimmed.startsWith(`"`) && trimmed.endsWith(`"`) || trimmed.startsWith(`'`) && trimmed.endsWith(`'`)) {
42049
+ return trimmed.slice(1, -1).trim();
42050
+ }
42051
+ return trimmed;
42052
+ };
42053
+ var addError = (errors, code, message) => {
42054
+ errors.push({ code, message });
42055
+ };
42056
+ var addWarning = (warnings, code, message) => {
42057
+ warnings.push({ code, message });
42058
+ };
42059
+ var isValidSkillName = (name) => {
42060
+ return SKILL_NAME_PATTERN.test(name) && !name.includes(`--`);
42061
+ };
42062
+ var assertSafeSkillTarget = (rootDir, skillName) => {
42063
+ if (!isValidSkillName(skillName)) {
42064
+ throw new Error(`Invalid skill name "${skillName}". Use lowercase letters, digits, and single hyphens only.`);
42065
+ }
42066
+ const root = import_node_path12.default.resolve(rootDir);
42067
+ const target = import_node_path12.default.resolve(root, skillName);
42068
+ if (target !== import_node_path12.default.join(root, skillName) || !target.startsWith(root + import_node_path12.default.sep)) {
42069
+ throw new Error(`Invalid skill install target for "${skillName}".`);
42070
+ }
42071
+ return target;
42072
+ };
42073
+ var parseSkillDocument = (skillMd) => {
42074
+ const fmMatch = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
42075
+ if (!fmMatch) return null;
42076
+ const frontmatter = {};
42077
+ for (const rawLine of fmMatch[1].split(/\r?\n/)) {
42078
+ const line = rawLine.trim();
42079
+ if (!line || line.startsWith(`#`)) continue;
42080
+ const match2 = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
42081
+ if (!match2) {
42082
+ frontmatter[`__invalid__${Object.keys(frontmatter).length}`] = line;
42083
+ continue;
42084
+ }
42085
+ frontmatter[match2[1]] = stripYamlQuotes(match2[2]);
42086
+ }
42087
+ return {
42088
+ name: frontmatter.name ?? ``,
42089
+ description: frontmatter.description ?? ``,
42090
+ body: skillMd.slice(fmMatch[0].length).trim(),
42091
+ frontmatter
42092
+ };
42093
+ };
42094
+ var validateSkillDocument = (input) => {
42095
+ const errors = [];
42096
+ const warnings = [];
42097
+ const skillMd = input.skillMd;
42098
+ if (!skillMd || !skillMd.trim()) {
42099
+ addError(errors, `empty_skill_md`, `SKILL.md content is required.`);
42100
+ return { ok: false, errors, warnings };
42101
+ }
42102
+ const parsed = parseSkillDocument(skillMd);
42103
+ if (!parsed) {
42104
+ addError(errors, `missing_frontmatter`, `SKILL.md must start with YAML frontmatter delimited by "---".`);
42105
+ return { ok: false, errors, warnings };
42106
+ }
42107
+ const invalidFrontmatterLines = Object.keys(parsed.frontmatter).filter((key) => key.startsWith(`__invalid__`));
42108
+ if (invalidFrontmatterLines.length > 0) {
42109
+ addError(errors, `invalid_frontmatter`, `Frontmatter must contain simple "key: value" lines.`);
42110
+ }
42111
+ if (!parsed.name) {
42112
+ addError(errors, `missing_name`, `Frontmatter field "name" is required.`);
42113
+ } else if (!isValidSkillName(parsed.name)) {
42114
+ addError(errors, `invalid_name`, `Skill name "${parsed.name}" must be kebab-case, under 64 characters, and path-safe.`);
42115
+ }
42116
+ if (!parsed.description) {
42117
+ addError(errors, `missing_description`, `Frontmatter field "description" is required.`);
42118
+ } else if (parsed.description.length > 500) {
42119
+ addWarning(warnings, `long_description`, `Skill description is long; keep trigger metadata concise.`);
42120
+ }
42121
+ if (input.skillName && parsed.name && input.skillName !== parsed.name) {
42122
+ addError(errors, `name_mismatch`, `Input skillName "${input.skillName}" must match frontmatter name "${parsed.name}".`);
42123
+ }
42124
+ if (input.description && parsed.description && input.description !== parsed.description) {
42125
+ addError(errors, `description_mismatch`, `Input description must match frontmatter description.`);
42126
+ }
42127
+ if (!parsed.body) {
42128
+ addError(errors, `empty_body`, `SKILL.md body is required.`);
42129
+ } else {
42130
+ if (!/(^|\n)#\s+\S/.test(parsed.body)) {
42131
+ addWarning(warnings, `missing_title`, `Skill body should start with a Markdown H1 title.`);
42132
+ }
42133
+ for (const sectionPattern of REQUIRED_SECTIONS) {
42134
+ if (!sectionPattern.test(parsed.body)) {
42135
+ addWarning(warnings, `missing_recommended_section`, `Skill body should include when-to-use and execution workflow sections.`);
42136
+ break;
42137
+ }
42138
+ }
42139
+ }
42140
+ if (input.baseDir) {
42141
+ const normalizedBase = import_node_path12.default.resolve(input.baseDir);
42142
+ for (const match2 of parsed.body.matchAll(/\]\(([^)]+)\)/g)) {
42143
+ const href = match2[1].trim();
42144
+ if (!href || /^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith(`#`)) continue;
42145
+ const target = import_node_path12.default.resolve(normalizedBase, href.split(`#`)[0]);
42146
+ if (!target.startsWith(normalizedBase + import_node_path12.default.sep) && target !== normalizedBase) {
42147
+ addError(errors, `unsafe_reference`, `Relative link "${href}" escapes the skill directory.`);
42148
+ } else if (!(0, import_node_fs3.existsSync)(target)) {
42149
+ addError(errors, `missing_reference`, `Relative link "${href}" does not exist in the skill directory.`);
42150
+ }
42151
+ }
42152
+ }
42153
+ return {
42154
+ ok: errors.length === 0,
42155
+ skill: parsed,
42156
+ errors,
42157
+ warnings
42158
+ };
42159
+ };
42160
+ var mergeIssues = (target, source) => {
42161
+ target.errors.push(...source.errors);
42162
+ target.warnings.push(...source.warnings);
42163
+ };
42164
+ var validateScriptsDirectory = (skillDir, warnings) => {
42165
+ const scriptsDir = import_node_path12.default.join(skillDir, `scripts`);
42166
+ if (!(0, import_node_fs3.existsSync)(scriptsDir)) return;
42167
+ const entries = (0, import_node_fs3.readdirSync)(scriptsDir, { withFileTypes: true }).filter((entry) => entry.isFile());
42168
+ if (entries.length === 0) {
42169
+ addWarning(warnings, `empty_scripts_dir`, `scripts/ exists but contains no files.`);
42170
+ }
42171
+ };
42172
+ var validateSkillDirectory = (skillDir, options = {}) => {
42173
+ const errors = [];
42174
+ const warnings = [];
42175
+ const normalizedSkillDir = import_node_path12.default.resolve(skillDir);
42176
+ const skillMdPath = import_node_path12.default.join(normalizedSkillDir, `SKILL.md`);
42177
+ if (!(0, import_node_fs3.existsSync)(normalizedSkillDir) || !(0, import_node_fs3.statSync)(normalizedSkillDir).isDirectory()) {
42178
+ addError(errors, `missing_skill_dir`, `Skill directory does not exist: ${normalizedSkillDir}`);
42179
+ return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
42180
+ }
42181
+ if (!(0, import_node_fs3.existsSync)(skillMdPath) || !(0, import_node_fs3.statSync)(skillMdPath).isFile()) {
42182
+ addError(errors, `missing_skill_md`, `Skill directory must contain SKILL.md.`);
42183
+ return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
42184
+ }
42185
+ const raw2 = (0, import_node_fs3.readFileSync)(skillMdPath, `utf-8`);
42186
+ const documentResult = validateSkillDocument({
42187
+ skillName: options.expectedName ?? import_node_path12.default.basename(normalizedSkillDir),
42188
+ skillMd: raw2,
42189
+ baseDir: normalizedSkillDir
42190
+ });
42191
+ mergeIssues({ errors, warnings }, documentResult);
42192
+ validateScriptsDirectory(normalizedSkillDir, warnings);
42193
+ return {
42194
+ ok: errors.length === 0,
42195
+ skill: documentResult.skill,
42196
+ errors,
42197
+ warnings,
42198
+ skillDir: normalizedSkillDir,
42199
+ skillMdPath
42200
+ };
42201
+ };
42202
+ var smokeTestSkillDirectory = (skillDir, options = {}) => {
42203
+ const directoryResult = validateSkillDirectory(skillDir, options);
42204
+ const errors = [...directoryResult.errors];
42205
+ const warnings = [...directoryResult.warnings];
42206
+ let detail;
42207
+ if (directoryResult.ok && directoryResult.skill) {
42208
+ detail = `Base directory for this skill: ${directoryResult.skillDir}
42209
+
42210
+ ${directoryResult.skill.body}`;
42211
+ if (!detail.includes(directoryResult.skillDir)) {
42212
+ addError(errors, `smoke_missing_base_dir`, `Skill detail does not include its base directory.`);
42213
+ }
42214
+ if (!detail.includes(directoryResult.skill.body)) {
42215
+ addError(errors, `smoke_missing_body`, `Skill detail does not include the SKILL.md body.`);
42216
+ }
42217
+ if (detail.length < 80) {
42218
+ addWarning(warnings, `smoke_short_detail`, `Skill detail is unusually short.`);
42219
+ }
42220
+ }
42221
+ return {
42222
+ ...directoryResult,
42223
+ ok: errors.length === 0,
42224
+ errors,
42225
+ warnings,
42226
+ detail
42227
+ };
42228
+ };
42229
+ var formatSkillValidationIssues = (result) => {
42230
+ const lines = [
42231
+ ...result.errors.map((issue) => `- [${issue.code}] ${issue.message}`),
42232
+ ...result.warnings.map((issue) => `- [warning:${issue.code}] ${issue.message}`)
42233
+ ];
42234
+ return lines.join(`
42235
+ `);
42236
+ };
42237
+
42238
+ // src/department/learning.ts
42239
+ var ensureDir = (dir) => (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
42240
+ var readJsonArray = (filePath) => {
42241
+ if (!(0, import_node_fs4.existsSync)(filePath)) return [];
42242
+ return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
42243
+ };
42244
+ var writeJsonArray = (filePath, records) => {
42245
+ ensureDir(import_node_path13.default.dirname(filePath));
42246
+ (0, import_node_fs4.writeFileSync)(filePath, JSON.stringify(records, null, " "), "utf-8");
42247
+ };
42248
+ var departmentMemoryPath = (departmentName) => {
42249
+ return import_node_path13.default.join(getDepartmentWorkSpaceDir(departmentName), "department-memory.json");
42250
+ };
42251
+ var departmentSkillPath = (departmentName) => {
42252
+ return import_node_path13.default.join(getDepartmentWorkSpaceDir(departmentName), "department-skills.json");
42253
+ };
42254
+ var departmentSkillDir = (departmentName, skillName) => {
42255
+ return assertSafeSkillTarget(import_node_path13.default.join(getDepartmentWorkSpaceDir(departmentName), "skills"), skillName);
42256
+ };
42257
+ var proposalsPath = () => {
42258
+ return import_node_path13.default.join(getDepartmentBaseDir(), "department-proposals.json");
42259
+ };
42260
+ var getDepartmentNameForHead = (request) => {
42261
+ const mailboxId = request?.departmentAgentId;
42262
+ if (!mailboxId) return null;
42263
+ const member = getDepartmentMemberByMailboxId(mailboxId);
42264
+ if (!member || member.role !== "department_head") return null;
42265
+ const [departmentName] = mailboxId.split("::");
42266
+ return departmentName || null;
42267
+ };
42268
+ var listDepartmentMemories = (departmentName) => {
42269
+ return readJsonArray(departmentMemoryPath(departmentName)).sort((a, b) => b.updatedAt - a.updatedAt);
42270
+ };
42271
+ var createDepartmentMemory = (departmentName, input) => {
42272
+ const now = Date.now();
42273
+ const memory = {
42274
+ id: (0, import_node_crypto4.randomUUID)().slice(0, 8),
42275
+ departmentName,
42276
+ title: input.title,
42277
+ content: input.content,
42278
+ sourceMailboxId: input.sourceMailboxId,
42279
+ createdAt: now,
42280
+ updatedAt: now
42281
+ };
42282
+ const records = listDepartmentMemories(departmentName);
42283
+ records.push(memory);
42284
+ writeJsonArray(departmentMemoryPath(departmentName), records);
42285
+ return memory;
42286
+ };
42287
+ var updateDepartmentMemory = (departmentName, id, patch) => {
42288
+ const records = listDepartmentMemories(departmentName);
42289
+ const idx = records.findIndex((record) => record.id === id);
42290
+ if (idx < 0) return null;
42291
+ records[idx] = {
42292
+ ...records[idx],
42293
+ ...patch.title !== void 0 ? { title: patch.title } : {},
42294
+ ...patch.content !== void 0 ? { content: patch.content } : {},
42295
+ updatedAt: Date.now()
42296
+ };
42297
+ writeJsonArray(departmentMemoryPath(departmentName), records);
42298
+ return records[idx];
42299
+ };
42300
+ var deleteDepartmentMemory = (departmentName, id) => {
42301
+ const records = listDepartmentMemories(departmentName);
42302
+ const target = records.find((record) => record.id === id) ?? null;
42303
+ writeJsonArray(departmentMemoryPath(departmentName), records.filter((record) => record.id !== id));
42304
+ return target;
42305
+ };
42306
+ var listDepartmentSkills = (departmentName) => {
42307
+ return readJsonArray(departmentSkillPath(departmentName)).sort((a, b) => b.updatedAt - a.updatedAt);
42308
+ };
42309
+ var proposeDepartmentSkill = (departmentName, input) => {
42310
+ const validation = validateSkillDocument({
42311
+ skillName: input.skillName,
42312
+ description: input.description,
42313
+ skillMd: input.skillMd
42314
+ });
42315
+ if (!validation.ok) {
42316
+ throw new Error(`[departmentSkill] Skill \u8349\u7A3F\u6821\u9A8C\u5931\u8D25\uFF1A
42317
+ ${formatSkillValidationIssues(validation)}`);
42318
+ }
42319
+ const records = listDepartmentSkills(departmentName);
42320
+ if (records.some((record) => record.skillName === input.skillName && record.status !== "dropped")) {
42321
+ return null;
42322
+ }
42323
+ const now = Date.now();
42324
+ const skill = {
42325
+ id: (0, import_node_crypto4.randomUUID)().slice(0, 8),
42326
+ departmentName,
42327
+ skillName: input.skillName,
42328
+ description: input.description,
42329
+ skillMd: input.skillMd,
42330
+ status: "pending",
42331
+ createdByMailboxId: input.createdByMailboxId,
42332
+ createdAt: now,
42333
+ updatedAt: now
42334
+ };
42335
+ records.push(skill);
42336
+ writeJsonArray(departmentSkillPath(departmentName), records);
42337
+ return skill;
42338
+ };
42339
+ var keepDepartmentSkill = (departmentName, id) => {
42340
+ const records = listDepartmentSkills(departmentName);
42341
+ const idx = records.findIndex((record) => record.id === id);
42342
+ if (idx < 0) return null;
42343
+ records[idx] = { ...records[idx], status: "active", updatedAt: Date.now() };
42344
+ const skillDir = departmentSkillDir(departmentName, records[idx].skillName);
42345
+ const validation = validateSkillDocument({
42346
+ skillName: records[idx].skillName,
42347
+ description: records[idx].description,
42348
+ skillMd: records[idx].skillMd,
42349
+ baseDir: skillDir
42350
+ });
42351
+ if (!validation.ok) {
42352
+ throw new Error(`[departmentSkill] Skill \u8349\u7A3F\u6821\u9A8C\u5931\u8D25\uFF0C\u62D2\u7EDD\u4FDD\u7559\uFF1A
42353
+ ${formatSkillValidationIssues(validation)}`);
42354
+ }
42355
+ ensureDir(skillDir);
42356
+ (0, import_node_fs4.writeFileSync)(import_node_path13.default.join(skillDir, "SKILL.md"), records[idx].skillMd, "utf-8");
42357
+ const smokeTest = smokeTestSkillDirectory(skillDir, { expectedName: records[idx].skillName });
42358
+ if (!smokeTest.ok) {
42359
+ throw new Error(`[departmentSkill] Skill smoke test \u5931\u8D25\uFF1A
42360
+ ${formatSkillValidationIssues(smokeTest)}`);
42361
+ }
42362
+ writeJsonArray(departmentSkillPath(departmentName), records);
42363
+ return records[idx];
41817
42364
  };
41818
- var resolveWebDistRoot = () => {
41819
- const coreRoot = resolveCoreRoot();
41820
- const candidates = [
41821
- import_path15.default.join(coreRoot, "dist", "web"),
41822
- import_path15.default.join(coreRoot, "web", "dist")
41823
- ];
41824
- for (const candidate of candidates) {
41825
- if ((0, import_fs10.existsSync)(import_path15.default.join(candidate, "index.html"))) {
41826
- return candidate;
41827
- }
41828
- }
41829
- return candidates[0];
42365
+ var dropDepartmentSkill = (departmentName, id) => {
42366
+ const records = listDepartmentSkills(departmentName);
42367
+ const idx = records.findIndex((record) => record.id === id);
42368
+ if (idx < 0) return null;
42369
+ records[idx] = { ...records[idx], status: "dropped", updatedAt: Date.now() };
42370
+ writeJsonArray(departmentSkillPath(departmentName), records);
42371
+ return records[idx];
42372
+ };
42373
+ var createDepartmentProposal = (input) => {
42374
+ const records = readJsonArray(proposalsPath());
42375
+ const proposal = {
42376
+ ...input,
42377
+ id: (0, import_node_crypto4.randomUUID)().slice(0, 8),
42378
+ status: "pending",
42379
+ createdAt: Date.now()
42380
+ };
42381
+ records.push(proposal);
42382
+ writeJsonArray(proposalsPath(), records);
42383
+ return proposal;
42384
+ };
42385
+ var buildDepartmentLearningContext = (departmentName) => {
42386
+ if (!departmentName) return "";
42387
+ const memories = listDepartmentMemories(departmentName);
42388
+ const activeSkills = listDepartmentSkills(departmentName).filter((skill) => skill.status === "active");
42389
+ if (memories.length === 0 && activeSkills.length === 0) return "";
42390
+ const memoryLines = memories.map(
42391
+ (memory) => ` - [id=${memory.id}] ${memory.title}
42392
+ ${memory.content.replace(/\n/g, "\n ")}`
42393
+ ).join("\n");
42394
+ const skillLines = activeSkills.map(
42395
+ (skill) => ` - ${skill.skillName}: ${skill.description}`
42396
+ ).join("\n");
42397
+ return [
42398
+ `<department-learning-context department="${departmentName}">`,
42399
+ memories.length > 0 ? `Department memories:
42400
+ ${memoryLines}` : "",
42401
+ activeSkills.length > 0 ? `Department skills:
42402
+ ${skillLines}` : "",
42403
+ `</department-learning-context>`
42404
+ ].filter(Boolean).join("\n");
41830
42405
  };
41831
42406
 
41832
42407
  // src/skill/SkillRegistry.ts
41833
42408
  var getProjectSkillsPath = () => {
41834
42409
  const projectRoot = findProjectRoot();
41835
- return projectRoot ? (0, import_path16.join)(projectRoot, "skills") : null;
42410
+ return projectRoot ? (0, import_path18.join)(projectRoot, "skills") : null;
41836
42411
  };
41837
42412
  var getSkillPaths = () => {
41838
42413
  const paths = [];
41839
42414
  const seenPaths = /* @__PURE__ */ new Set();
41840
42415
  const pushPath = (candidate) => {
41841
- if (!(0, import_fs11.existsSync)(candidate) || !(0, import_fs11.statSync)(candidate).isDirectory()) return;
41842
- const normalized = import_path16.default.resolve(candidate);
42416
+ if (!(0, import_fs13.existsSync)(candidate) || !(0, import_fs13.statSync)(candidate).isDirectory()) return;
42417
+ const normalized = import_path18.default.resolve(candidate);
41843
42418
  if (seenPaths.has(normalized)) return;
41844
42419
  seenPaths.add(normalized);
41845
42420
  paths.push(normalized);
@@ -41848,16 +42423,16 @@ var getSkillPaths = () => {
41848
42423
  if (projectSkillsPath) {
41849
42424
  pushPath(projectSkillsPath);
41850
42425
  }
41851
- pushPath((0, import_path16.join)((0, import_os3.homedir)(), ".duclaw", "skills"));
41852
- pushPath((0, import_path16.join)((0, import_os3.homedir)(), ".agents", "skills"));
42426
+ pushPath((0, import_path18.join)((0, import_os3.homedir)(), ".duclaw", "skills"));
42427
+ pushPath((0, import_path18.join)((0, import_os3.homedir)(), ".agents", "skills"));
41853
42428
  return paths;
41854
42429
  };
41855
42430
  var getDirectories = (dirPath) => {
41856
- return (0, import_fs11.readdirSync)(dirPath).filter((name) => (0, import_fs11.statSync)((0, import_path16.join)(dirPath, name)).isDirectory());
42431
+ return (0, import_fs13.readdirSync)(dirPath).filter((name) => (0, import_fs13.statSync)((0, import_path18.join)(dirPath, name)).isDirectory());
41857
42432
  };
41858
- var parseSkill = (mdPath, skillDir) => {
41859
- if (!(0, import_fs11.existsSync)(mdPath)) return null;
41860
- const raw2 = (0, import_fs11.readFileSync)(mdPath, "utf-8");
42433
+ var parseSkill = (mdPath, skillDir, options = {}) => {
42434
+ if (!(0, import_fs13.existsSync)(mdPath)) return null;
42435
+ const raw2 = (0, import_fs13.readFileSync)(mdPath, "utf-8");
41861
42436
  let name = "";
41862
42437
  let description = "";
41863
42438
  let body = raw2;
@@ -41873,18 +42448,22 @@ var parseSkill = (mdPath, skillDir) => {
41873
42448
  }
41874
42449
  }
41875
42450
  if (!name) {
41876
- name = import_path16.default.basename(import_path16.default.dirname(mdPath));
42451
+ name = import_path18.default.basename(import_path18.default.dirname(mdPath));
41877
42452
  }
41878
42453
  if (!description && body) {
41879
42454
  description = body.split("\n")[0].replace(/^#+\s*/, "").trim();
41880
42455
  }
42456
+ const normalizedSkillDir = import_path18.default.resolve(skillDir);
41881
42457
  const projectSkillsPath = getProjectSkillsPath();
41882
- const normalizedSkillDir = import_path16.default.resolve(skillDir);
41883
- const deletable = projectSkillsPath ? normalizedSkillDir === import_path16.default.resolve(projectSkillsPath, import_path16.default.basename(normalizedSkillDir)) && normalizedSkillDir.startsWith(import_path16.default.resolve(projectSkillsPath) + import_path16.default.sep) : false;
42458
+ const isProjectSkill = projectSkillsPath ? normalizedSkillDir === import_path18.default.resolve(projectSkillsPath, import_path18.default.basename(normalizedSkillDir)) && normalizedSkillDir.startsWith(import_path18.default.resolve(projectSkillsPath) + import_path18.default.sep) : false;
42459
+ const scope = options.scope ?? (isProjectSkill ? "project" : "global");
42460
+ const deletable = options.deletable ?? (scope === "project" && isProjectSkill);
41884
42461
  return {
41885
42462
  name,
41886
42463
  description,
41887
42464
  baseDir: skillDir,
42465
+ scope,
42466
+ departmentName: options.departmentName,
41888
42467
  deletable,
41889
42468
  getDetail: () => {
41890
42469
  let content = body.replace(/<Skill目录>/g, skillDir).replace(/\$\{CLAUDE_SKILL_DIR\}/g, skillDir);
@@ -41894,16 +42473,35 @@ ${content}`;
41894
42473
  }
41895
42474
  };
41896
42475
  };
42476
+ var loadDepartmentSkills = (seen) => {
42477
+ const skills = [];
42478
+ for (const department of listDepartments()) {
42479
+ const activeSkills = listDepartmentSkills(department.name).filter((skill) => skill.status === "active");
42480
+ for (const departmentSkill of activeSkills) {
42481
+ if (seen.has(departmentSkill.skillName)) continue;
42482
+ const skillDir = (0, import_path18.join)(getDepartmentWorkSpaceDir(department.name), "skills", departmentSkill.skillName);
42483
+ const skill = parseSkill((0, import_path18.join)(skillDir, "SKILL.md"), skillDir, {
42484
+ scope: "department",
42485
+ departmentName: department.name,
42486
+ deletable: false
42487
+ });
42488
+ if (skill) {
42489
+ seen.add(skill.name);
42490
+ skills.push(skill);
42491
+ }
42492
+ }
42493
+ }
42494
+ return skills;
42495
+ };
41897
42496
  var loadSkill = () => {
41898
42497
  const skillPaths = getSkillPaths();
41899
- if (skillPaths.length === 0) return [];
41900
42498
  const seen = /* @__PURE__ */ new Set();
41901
42499
  const skills = [];
41902
42500
  for (const skillPath of skillPaths) {
41903
42501
  const dirs = getDirectories(skillPath);
41904
42502
  for (const skillName of dirs) {
41905
- const eachSkillPath = (0, import_path16.join)(skillPath, skillName);
41906
- const eachSkillMdPath = (0, import_path16.join)(eachSkillPath, "SKILL.md");
42503
+ const eachSkillPath = (0, import_path18.join)(skillPath, skillName);
42504
+ const eachSkillMdPath = (0, import_path18.join)(eachSkillPath, "SKILL.md");
41907
42505
  const skill = parseSkill(eachSkillMdPath, eachSkillPath);
41908
42506
  if (skill && !seen.has(skill.name)) {
41909
42507
  seen.add(skill.name);
@@ -41911,13 +42509,14 @@ var loadSkill = () => {
41911
42509
  }
41912
42510
  }
41913
42511
  }
42512
+ skills.push(...loadDepartmentSkills(seen));
41914
42513
  return skills;
41915
42514
  };
41916
42515
  var deleteSkill = (name) => {
41917
42516
  const skills = loadSkill();
41918
42517
  const skill = skills.find((s) => s.name === name);
41919
42518
  if (!skill || !skill.deletable) return false;
41920
- (0, import_fs11.rmSync)(skill.baseDir, { recursive: true, force: false });
42519
+ (0, import_fs13.rmSync)(skill.baseDir, { recursive: true, force: false });
41921
42520
  return true;
41922
42521
  };
41923
42522
  var SkillRegistry_default = loadSkill;
@@ -42204,10 +42803,10 @@ var goalDelete = {
42204
42803
  };
42205
42804
 
42206
42805
  // src/tools/tools/department/DepartmentCreate.ts
42207
- var import_node_crypto8 = require("node:crypto");
42806
+ var import_node_crypto9 = require("node:crypto");
42208
42807
 
42209
42808
  // src/department/mailbox/mailbox.ts
42210
- var import_node_crypto7 = require("node:crypto");
42809
+ var import_node_crypto8 = require("node:crypto");
42211
42810
 
42212
42811
  // src/agent/interruptRegistry.ts
42213
42812
  var registry = /* @__PURE__ */ new Map();
@@ -42258,7 +42857,7 @@ var drainInterrupts = (userId) => {
42258
42857
  };
42259
42858
 
42260
42859
  // src/agent/events.ts
42261
- var import_node_crypto4 = require("node:crypto");
42860
+ var import_node_crypto5 = require("node:crypto");
42262
42861
  var rowToEvent = (row) => ({
42263
42862
  id: row.id,
42264
42863
  userId: row.userId,
@@ -42275,7 +42874,7 @@ var rowToEvent = (row) => ({
42275
42874
  var recordAgentEvent = (input) => {
42276
42875
  const db3 = createSqliteDB();
42277
42876
  const now = Date.now();
42278
- const id = `evt_${(0, import_node_crypto4.randomUUID)().slice(0, 12)}`;
42877
+ const id = `evt_${(0, import_node_crypto5.randomUUID)().slice(0, 12)}`;
42279
42878
  const payloadJson = JSON.stringify(input.payload);
42280
42879
  db3.prepare(`
42281
42880
  INSERT INTO agent_events (
@@ -42406,7 +43005,7 @@ ${ceoFollowupInstruction}
42406
43005
  };
42407
43006
 
42408
43007
  // src/department/mailbox/events.ts
42409
- var import_node_crypto5 = require("node:crypto");
43008
+ var import_node_crypto6 = require("node:crypto");
42410
43009
  var parseDetail = (detailJson) => {
42411
43010
  if (!detailJson) return void 0;
42412
43011
  try {
@@ -42447,7 +43046,7 @@ var mapMailboxEventRow = (row) => {
42447
43046
  var recordMailboxEvent = (input) => {
42448
43047
  const db3 = createSqliteDB();
42449
43048
  const event = {
42450
- id: (0, import_node_crypto5.randomUUID)().slice(0, 12),
43049
+ id: (0, import_node_crypto6.randomUUID)().slice(0, 12),
42451
43050
  messageId: input.messageId,
42452
43051
  mailboxId: input.mailboxId,
42453
43052
  actorMailboxId: input.actorMailboxId,
@@ -42516,222 +43115,21 @@ var listMailboxEvents = (params) => {
42516
43115
  m.send_time as sendTime,
42517
43116
  m.status as status,
42518
43117
  m.origin_user_id as originUserId,
42519
- m.origin_platform as originPlatform,
42520
- m.thread_id as threadId,
42521
- m.parent_message_id as parentMessageId
42522
- FROM mailbox_events e
42523
- LEFT JOIN mailbox m ON m.id = e.message_id
42524
- WHERE e.mailbox_id = ? OR e.actor_mailbox_id = ? OR e.counterpart_mailbox_id = ?
42525
- ORDER BY e.created_at DESC
42526
- LIMIT ?`
42527
- );
42528
- const rows = stmt.all(params.mailboxId, params.mailboxId, params.mailboxId, limit);
42529
- return rows.map(mapMailboxEventRow);
42530
- };
42531
-
42532
- // src/department/DepartmentMember.ts
42533
- var import_fs13 = require("fs");
42534
- var import_path18 = __toESM(require("path"));
42535
-
42536
- // src/department/Department.ts
42537
- var import_path17 = __toESM(require("path"));
42538
- var import_fs12 = require("fs");
42539
- var legacyMigrationChecked = false;
42540
- var getDepartmentBaseDir = () => {
42541
- return import_path17.default.join(getDuclawHomeDir(), "department");
42542
- };
42543
- var getLegacyTeamBaseDir = () => {
42544
- return import_path17.default.join(getDuclawHomeDir(), "team");
42545
- };
42546
- var getDepartmentWorkSpaceDir = (departmentName) => {
42547
- return import_path17.default.join(getDepartmentBaseDir(), "workspace", departmentName);
42548
- };
42549
- var getLegacyTeamWorkSpaceDir = (teamName) => {
42550
- return import_path17.default.join(getLegacyTeamBaseDir(), "workspace", teamName);
42551
- };
42552
- var getDepartmentJsonPath = (departmentName) => {
42553
- return import_path17.default.join(getDepartmentWorkSpaceDir(departmentName), "department.json");
42554
- };
42555
- var getLegacyTeamJsonPath = (teamName) => {
42556
- return import_path17.default.join(getLegacyTeamWorkSpaceDir(teamName), "team.json");
42557
- };
42558
- var mapLegacyRole = (role) => {
42559
- return role === "team_manager" ? "department_head" : "executor";
42560
- };
42561
- var mapLegacyDepartment = (legacy) => {
42562
- const departmentMembers = (legacy.teamMembers ?? []).map((member) => ({
42563
- id: member.id,
42564
- name: member.name,
42565
- departmentId: legacy.id,
42566
- mailBoxId: member.mailBoxId,
42567
- workspaceId: member.workspaceId,
42568
- role: mapLegacyRole(member.role),
42569
- focusOn: member.focusOn
42570
- }));
42571
- const headMemberId = legacy.managerMemberId ?? departmentMembers.find((member) => member.role === "department_head")?.id;
42572
- return {
42573
- id: legacy.id,
42574
- name: legacy.name,
42575
- sourceGoalId: legacy.goalId,
42576
- charter: legacy.goalId ? `Legacy department migrated from team goal ${legacy.goalId}.` : "Legacy department migrated from team data.",
42577
- workpath: legacy.workpath,
42578
- headMemberId,
42579
- departmentMembers
42580
- };
42581
- };
42582
- var migrateLegacyTeamsToDepartments = () => {
42583
- if (legacyMigrationChecked) return;
42584
- legacyMigrationChecked = true;
42585
- const legacyWorkspaceDir = import_path17.default.join(getLegacyTeamBaseDir(), "workspace");
42586
- if (!(0, import_fs12.existsSync)(legacyWorkspaceDir)) return;
42587
- for (const legacyName of (0, import_fs12.readdirSync)(legacyWorkspaceDir)) {
42588
- const legacyJsonPath = getLegacyTeamJsonPath(legacyName);
42589
- if (!(0, import_fs12.existsSync)(legacyJsonPath)) continue;
42590
- const departmentJsonPath = getDepartmentJsonPath(legacyName);
42591
- if ((0, import_fs12.existsSync)(departmentJsonPath)) continue;
42592
- try {
42593
- const legacy = JSON.parse((0, import_fs12.readFileSync)(legacyJsonPath, "utf-8"));
42594
- const department = mapLegacyDepartment(legacy);
42595
- (0, import_fs12.mkdirSync)(getDepartmentWorkSpaceDir(department.name), { recursive: true });
42596
- (0, import_fs12.writeFileSync)(departmentJsonPath, JSON.stringify(department, null, " "), "utf-8");
42597
- } catch (err) {
42598
- console.warn(`[department] Failed to migrate legacy team ${legacyName}: ${err.message}`);
42599
- }
42600
- }
42601
- };
42602
- var createDepartment = (departmentDefinition) => {
42603
- if (!departmentDefinition) throw new Error(`[createDepartment] departmentDefinition\u4E0D\u80FD\u4E3A\u7A7A`);
42604
- const departmentPath = getDepartmentWorkSpaceDir(departmentDefinition.name);
42605
- (0, import_fs12.mkdirSync)(departmentPath, { recursive: true });
42606
- (0, import_fs12.writeFileSync)(getDepartmentJsonPath(departmentDefinition.name), JSON.stringify(departmentDefinition, null, " "), "utf-8");
42607
- return departmentDefinition;
42608
- };
42609
- var getDepartment = (name) => {
42610
- migrateLegacyTeamsToDepartments();
42611
- const departmentJsonPath = getDepartmentJsonPath(name);
42612
- if (!(0, import_fs12.existsSync)(departmentJsonPath)) return null;
42613
- const text2 = (0, import_fs12.readFileSync)(departmentJsonPath, "utf-8");
42614
- return JSON.parse(text2);
42615
- };
42616
- var listDepartments = () => {
42617
- migrateLegacyTeamsToDepartments();
42618
- const workspaceDir = import_path17.default.join(getDepartmentBaseDir(), "workspace");
42619
- if (!(0, import_fs12.existsSync)(workspaceDir)) return [];
42620
- const departments = [];
42621
- for (const departmentName of (0, import_fs12.readdirSync)(workspaceDir)) {
42622
- const department = getDepartment(departmentName);
42623
- if (department) departments.push(department);
42624
- }
42625
- return departments;
42626
- };
42627
- var getDepartmentById = (id) => {
42628
- const department = listDepartments().find((item) => item.id === id);
42629
- return department ?? null;
42630
- };
42631
- var deleteDepartment = (name) => {
42632
- if (!(0, import_fs12.existsSync)(getDepartmentJsonPath(name))) {
42633
- throw new Error(`[deleteDepartment] \u4E0D\u5B58\u5728\u5BF9\u5E94\u7684\u90E8\u95E8 ${name} \u7684 department.json \u6587\u4EF6`);
42634
- }
42635
- (0, import_fs12.rmSync)(getDepartmentJsonPath(name));
42636
- };
42637
-
42638
- // src/department/workspace/workspace.ts
42639
- var db = createSqliteDB();
42640
- var getWorkspaceId = (departmentName, memberName) => {
42641
- return `${departmentName}::${memberName}`;
42642
- };
42643
- var createWorkspace = (workspace) => {
42644
- const stmt = db.prepare(`INSERT INTO workspace (id, team_name, teammate_name, team_workpath) VALUES (?, ?, ?, ?) `);
42645
- stmt.run(
42646
- workspace.id,
42647
- workspace.departmentName,
42648
- workspace.memberName,
42649
- workspace.departmentWorkPath
42650
- );
42651
- };
42652
- var deleteWorkspace = (workspaceId) => {
42653
- const stmt = db.prepare(`delete from workspace where id = ?`);
42654
- stmt.run(workspaceId);
42655
- };
42656
-
42657
- // src/department/DepartmentMember.ts
42658
- var createDepartmentMember = (departmentMemberDefinition) => {
42659
- if (!departmentMemberDefinition) throw new Error(`[createDepartmentMember] departmentMemberDefinition\u4E0D\u80FD\u4E3A\u7A7A`);
42660
- const { name, departmentId, workspaceId } = departmentMemberDefinition;
42661
- const department = getDepartmentById(departmentId);
42662
- if (!department) throw new Error(`[createDepartmentMember] \u627E\u4E0D\u5230\u5BF9\u5E94\u7684 department: ${departmentId}`);
42663
- const memberPath = import_path18.default.join(getDepartmentWorkSpaceDir(department.name), name);
42664
- (0, import_fs13.mkdirSync)(memberPath, { recursive: true });
42665
- const workspace = {
42666
- id: getWorkspaceId(department.name, departmentMemberDefinition.name),
42667
- departmentName: department.name,
42668
- memberName: name,
42669
- departmentWorkPath: memberPath
42670
- };
42671
- createWorkspace(workspace);
42672
- if (!department.departmentMembers) {
42673
- department.departmentMembers = [];
42674
- }
42675
- if (department.departmentMembers.some((member) => member.id === departmentMemberDefinition.id || member.name === departmentMemberDefinition.name)) {
42676
- throw new Error(`[createDepartmentMember] \u90E8\u95E8 ${department.name} \u5DF2\u5B58\u5728\u540C\u540D\u6216\u540C id \u6210\u5458: ${departmentMemberDefinition.name}/${departmentMemberDefinition.id}`);
42677
- }
42678
- if (departmentMemberDefinition.role === "department_head") {
42679
- const existingHead = department.headMemberId ? department.departmentMembers.find((member) => member.id === department.headMemberId) : department.departmentMembers.find((member) => member.role === "department_head");
42680
- if (existingHead) {
42681
- throw new Error(`[createDepartmentMember] \u90E8\u95E8 ${department.name} \u5DF2\u5B58\u5728 Department Head: ${existingHead.name}`);
42682
- }
42683
- department.headMemberId = departmentMemberDefinition.id;
42684
- }
42685
- department.departmentMembers.push(departmentMemberDefinition);
42686
- (0, import_fs13.writeFileSync)(getDepartmentJsonPath(department.name), JSON.stringify(department, null, " "), "utf-8");
42687
- return departmentMemberDefinition;
42688
- };
42689
- var listDepartmentMembers = (departmentName) => {
42690
- const departmentJsonPath = getDepartmentJsonPath(departmentName);
42691
- if (!(0, import_fs13.existsSync)(departmentJsonPath)) return [];
42692
- const text2 = (0, import_fs13.readFileSync)(departmentJsonPath, "utf-8");
42693
- const department = JSON.parse(text2);
42694
- if (!department) throw new Error(`[listDepartmentMembers] \u627E\u4E0D\u5230\u5BF9\u5E94\u7684 department: ${departmentName}`);
42695
- return department.departmentMembers ?? [];
42696
- };
42697
- var getDepartmentMember = (departmentName, departmentMemberId) => {
42698
- const members = listDepartmentMembers(departmentName);
42699
- return members.find((member) => member.id === departmentMemberId) || null;
42700
- };
42701
- var getDepartmentMemberByName = (departmentName, departmentMemberName) => {
42702
- const members = listDepartmentMembers(departmentName);
42703
- return members.find((member) => member.name === departmentMemberName) || null;
42704
- };
42705
- var getDepartmentMemberByMailboxId = (mailboxId) => {
42706
- const [departmentName, memberName] = mailboxId.split("::");
42707
- if (!departmentName || !memberName) return null;
42708
- return getDepartmentMemberByName(departmentName, memberName);
42709
- };
42710
- var getDepartmentMemberById = (memberId) => {
42711
- for (const department of listDepartments()) {
42712
- const targetMember = department.departmentMembers.find((member) => member.id === memberId);
42713
- if (targetMember) return targetMember;
42714
- }
42715
- return null;
42716
- };
42717
- var deleteDepartmentMemberById = (departmentName, memberId) => {
42718
- const department = getDepartment(departmentName);
42719
- if (!department) throw new Error(`[deleteDepartmentMemberById] \u627E\u4E0D\u5230\u5BF9\u5E94\u7684 department: ${departmentName}`);
42720
- const memberIdx = department.departmentMembers.findIndex((member) => member.id === memberId);
42721
- if (memberIdx === -1) {
42722
- throw new Error(`[deleteDepartmentMemberById] \u627E\u4E0D\u5230\u5BF9\u5E94 id \u7684 department member: ${memberId}`);
42723
- }
42724
- const workspaceId = department.departmentMembers[memberIdx].workspaceId;
42725
- if (department.headMemberId === memberId) {
42726
- delete department.headMemberId;
42727
- }
42728
- department.departmentMembers = department.departmentMembers.filter((_, index) => index !== memberIdx);
42729
- (0, import_fs13.writeFileSync)(getDepartmentJsonPath(departmentName), JSON.stringify(department, null, " "), "utf-8");
42730
- deleteWorkspace(workspaceId);
43118
+ m.origin_platform as originPlatform,
43119
+ m.thread_id as threadId,
43120
+ m.parent_message_id as parentMessageId
43121
+ FROM mailbox_events e
43122
+ LEFT JOIN mailbox m ON m.id = e.message_id
43123
+ WHERE e.mailbox_id = ? OR e.actor_mailbox_id = ? OR e.counterpart_mailbox_id = ?
43124
+ ORDER BY e.created_at DESC
43125
+ LIMIT ?`
43126
+ );
43127
+ const rows = stmt.all(params.mailboxId, params.mailboxId, params.mailboxId, limit);
43128
+ return rows.map(mapMailboxEventRow);
42731
43129
  };
42732
43130
 
42733
43131
  // src/department/mailbox/ceoFollowup.ts
42734
- var import_node_crypto6 = require("node:crypto");
43132
+ var import_node_crypto7 = require("node:crypto");
42735
43133
  var rowToFollowup = (row) => ({
42736
43134
  id: row.id,
42737
43135
  sourceMessageId: row.sourceMessageId,
@@ -42778,7 +43176,7 @@ var enqueueCeoFollowupFromMailbox = (message) => {
42778
43176
  if (!message.originUserId || !message.originPlatform) return null;
42779
43177
  const db3 = createSqliteDB();
42780
43178
  const now = Date.now();
42781
- const id = `cfu_${(0, import_node_crypto6.randomUUID)().slice(0, 12)}`;
43179
+ const id = `cfu_${(0, import_node_crypto7.randomUUID)().slice(0, 12)}`;
42782
43180
  db3.prepare(`
42783
43181
  INSERT INTO ceo_followups (
42784
43182
  id,
@@ -43108,7 +43506,7 @@ var recordMailboxReceivedAgentEvent = (msg) => {
43108
43506
  };
43109
43507
  var sendMessage2 = (fromMailboxId, toMailboxId, content, options) => {
43110
43508
  const db3 = createSqliteDB();
43111
- const id = (0, import_node_crypto7.randomUUID)().slice(0, 8);
43509
+ const id = (0, import_node_crypto8.randomUUID)().slice(0, 8);
43112
43510
  const threadId = options?.threadId || id;
43113
43511
  const workItemContext = resolveWorkItemContext(fromMailboxId, toMailboxId, id, options);
43114
43512
  const stmt = db3.prepare(`insert into mailbox (
@@ -43251,7 +43649,7 @@ var departmentCreate = {
43251
43649
  return `[departmentCreate] \u4E0D\u5B58\u5728 id=${sourceGoalId} \u7684\u76EE\u6807`;
43252
43650
  }
43253
43651
  let departmentDefinition = {
43254
- id: (0, import_node_crypto8.randomUUID)().slice(0, 8),
43652
+ id: (0, import_node_crypto9.randomUUID)().slice(0, 8),
43255
43653
  name,
43256
43654
  charter,
43257
43655
  sourceGoalId,
@@ -43518,7 +43916,7 @@ var departmentList = {
43518
43916
  };
43519
43917
 
43520
43918
  // src/tools/tools/department/DepartmentMemberCreate.ts
43521
- var import_node_crypto9 = require("node:crypto");
43919
+ var import_node_crypto10 = require("node:crypto");
43522
43920
  var DESCRIPTION24 = `
43523
43921
  \u521B\u5EFA\u90E8\u95E8\u6210\u5458\u3002
43524
43922
 
@@ -43584,7 +43982,7 @@ var departmentMemberCreate = {
43584
43982
  }
43585
43983
  }
43586
43984
  let departmentMember = {
43587
- id: (0, import_node_crypto9.randomUUID)().slice(0, 8),
43985
+ id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
43588
43986
  name,
43589
43987
  departmentId: department.id,
43590
43988
  mailBoxId: getMailBoxId(department.name, name),
@@ -43714,437 +44112,63 @@ var checkDepartmentReplies = {
43714
44112
  type: `object`,
43715
44113
  properties: {
43716
44114
  from_member: {
43717
- type: `string`,
43718
- description: `\u53EF\u9009\uFF1A\u53EA\u67E5\u770B\u6307\u5B9A mailboxId \u7684\u56DE\u4FE1\uFF0C\u4E0D\u586B\u5219\u67E5\u770B\u6240\u6709\u56DE\u4FE1`
43719
- }
43720
- },
43721
- required: []
43722
- },
43723
- async execute(input) {
43724
- const db3 = createSqliteDB();
43725
- const fromMember = input.from_member;
43726
- let msgs;
43727
- if (fromMember) {
43728
- const stmt = db3.prepare(
43729
- `SELECT id, to_mailbox_id as toMailboxId, from_mailbox_id as fromMailboxId, content, send_time as sendTime, status
43730
- FROM mailbox
43731
- WHERE to_mailbox_id = 'manager' AND from_mailbox_id = ? AND status in ('pending', 'processing')
43732
- ORDER BY send_time ASC`
43733
- );
43734
- msgs = stmt.all(fromMember);
43735
- } else {
43736
- const stmt = db3.prepare(
43737
- `SELECT id, to_mailbox_id as toMailboxId, from_mailbox_id as fromMailboxId, content, send_time as sendTime, status
43738
- FROM mailbox
43739
- WHERE to_mailbox_id = 'manager' AND status in ('pending', 'processing')
43740
- ORDER BY send_time ASC`
43741
- );
43742
- msgs = stmt.all();
43743
- }
43744
- if (msgs.length === 0) {
43745
- return [
43746
- `[checkDepartmentReplies] \u56E2\u961F\u8FD9\u4F1A\u513F\u8FD8\u6CA1\u56DE\u4FE1\uFF0C\u591A\u534A\u662F\u8D1F\u8D23\u4EBA\u4ECD\u5728\u5904\u7406\uFF0C\u8FC7\u4F1A\u513F\u518D\u6765\u770B\u3002`,
43747
- ``,
43748
- `\u51E0\u53E5\u63D0\u9192\uFF1A`,
43749
- `- \u8FD9\u4E0D\u4EE3\u8868\u8981\u4F60\u4EB2\u81EA\u4E0B\u573A read/bash/\u6D4B\u8BD5/\u6539\u4EE3\u7801\u2014\u2014\u56E2\u961F\u6CA1\u56DE\uFF0C\u4E0D\u662F\u4F60\u8BE5\u63A5\u624B\u7684\u4FE1\u53F7\u3002`,
43750
- `- \u8001\u677F\u8981\u662F\u6B63\u7B49\u7740\uFF0C\u5148\u7528 send_message \u5982\u5B9E\u8DDF\u4ED6\u8BF4\u4E00\u53E5\u201C\u8D1F\u8D23\u4EBA\u8FD8\u5728\u5904\u7406\uFF0C\u6211\u62FF\u5230\u7ED3\u679C\u5C31\u540C\u6B65\u201D\u3002`,
43751
- `- \u5982\u679C\u4EFB\u52A1\u521A\u6D3E\u51FA\u53BB\uFF0C\u4E0D\u8981\u5728\u540C\u4E00\u8F6E\u9A6C\u4E0A\u50AC\u529E\uFF1B\u7ED9\u8D1F\u8D23\u4EBA\u5408\u7406\u5904\u7406\u65F6\u95F4\u3002`,
43752
- `- \u5DF2\u7ECF\u7B49\u4E86\u4E00\u9635\u3001\u8001\u677F\u53C8\u8FFD\u95EE\uFF0C\u6216\u53D1\u73B0\u660E\u786E\u98CE\u9669\u65F6\uFF0C\u518D\u7528 mailbox_followup \u6216 department_communicate \u53BB\u95EE\u5BF9\u5E94 Department Head \u771F\u5B9E\u8FDB\u5C55\u3001\u5361\u5728\u54EA\u3001\u5927\u6982\u4EC0\u4E48\u65F6\u5019\u597D\u3002`,
43753
- `- \u522B\u5728\u8FD9\u4E00\u8F6E\u91CC\u53CD\u590D\u7A7A\u67E5 check_department_replies\uFF1B\u7B49\u56E2\u961F\u56DE\u4FE1\u3001\u8001\u677F\u8FFD\u95EE\u6216\u50AC\u529E\u6709\u4E86\u56DE\u97F3\uFF0C\u518D\u6765\u770B\u3002`
43754
- ].join("\n");
43755
- }
43756
- const updateStmt = db3.prepare(`UPDATE mailbox SET status = 'read' WHERE id = ?`);
43757
- for (const msg of msgs) {
43758
- updateStmt.run(msg.id);
43759
- }
43760
- const replies = msgs.map((msg, i) => {
43761
- const time = new Date(msg.sendTime).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
43762
- return `--- \u56DE\u4FE1 ${i + 1} ---
43763
- id: ${msg.id}
43764
- \u6765\u81EA: ${msg.fromMailboxId}
43765
- \u65F6\u95F4: ${time}
43766
- \u72B6\u6001: ${msg.status}
43767
- \u5185\u5BB9:
43768
- ${msg.content}`;
43769
- }).join("\n\n");
43770
- return `[checkDepartmentReplies] \u6536\u5230 ${msgs.length} \u6761\u90E8\u95E8\u56DE\u4FE1\uFF1A
43771
-
43772
- ${replies}`;
43773
- }
43774
- };
43775
-
43776
- // src/department/learning.ts
43777
- var import_node_fs4 = require("node:fs");
43778
- var import_node_path13 = __toESM(require("node:path"));
43779
- var import_node_crypto10 = require("node:crypto");
43780
-
43781
- // src/skill/SkillValidator.ts
43782
- var import_node_fs3 = require("node:fs");
43783
- var import_node_path12 = __toESM(require("node:path"));
43784
- var SKILL_NAME_PATTERN = /^[a-z0-9](?:[a-z0-9-]{0,62}[a-z0-9])?$/;
43785
- var REQUIRED_SECTIONS = [
43786
- /(^|\n)##\s+(when to use|何时使用|trigger|triggers)\b/i,
43787
- /(^|\n)##\s+(steps|workflow|procedure|执行步骤|工作流)\b/i
43788
- ];
43789
- var stripYamlQuotes = (value) => {
43790
- const trimmed = value.trim();
43791
- if (trimmed.startsWith(`"`) && trimmed.endsWith(`"`) || trimmed.startsWith(`'`) && trimmed.endsWith(`'`)) {
43792
- return trimmed.slice(1, -1).trim();
43793
- }
43794
- return trimmed;
43795
- };
43796
- var addError = (errors, code, message) => {
43797
- errors.push({ code, message });
43798
- };
43799
- var addWarning = (warnings, code, message) => {
43800
- warnings.push({ code, message });
43801
- };
43802
- var isValidSkillName = (name) => {
43803
- return SKILL_NAME_PATTERN.test(name) && !name.includes(`--`);
43804
- };
43805
- var assertSafeSkillTarget = (rootDir, skillName) => {
43806
- if (!isValidSkillName(skillName)) {
43807
- throw new Error(`Invalid skill name "${skillName}". Use lowercase letters, digits, and single hyphens only.`);
43808
- }
43809
- const root = import_node_path12.default.resolve(rootDir);
43810
- const target = import_node_path12.default.resolve(root, skillName);
43811
- if (target !== import_node_path12.default.join(root, skillName) || !target.startsWith(root + import_node_path12.default.sep)) {
43812
- throw new Error(`Invalid skill install target for "${skillName}".`);
43813
- }
43814
- return target;
43815
- };
43816
- var parseSkillDocument = (skillMd) => {
43817
- const fmMatch = skillMd.match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/);
43818
- if (!fmMatch) return null;
43819
- const frontmatter = {};
43820
- for (const rawLine of fmMatch[1].split(/\r?\n/)) {
43821
- const line = rawLine.trim();
43822
- if (!line || line.startsWith(`#`)) continue;
43823
- const match2 = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
43824
- if (!match2) {
43825
- frontmatter[`__invalid__${Object.keys(frontmatter).length}`] = line;
43826
- continue;
43827
- }
43828
- frontmatter[match2[1]] = stripYamlQuotes(match2[2]);
43829
- }
43830
- return {
43831
- name: frontmatter.name ?? ``,
43832
- description: frontmatter.description ?? ``,
43833
- body: skillMd.slice(fmMatch[0].length).trim(),
43834
- frontmatter
43835
- };
43836
- };
43837
- var validateSkillDocument = (input) => {
43838
- const errors = [];
43839
- const warnings = [];
43840
- const skillMd = input.skillMd;
43841
- if (!skillMd || !skillMd.trim()) {
43842
- addError(errors, `empty_skill_md`, `SKILL.md content is required.`);
43843
- return { ok: false, errors, warnings };
43844
- }
43845
- const parsed = parseSkillDocument(skillMd);
43846
- if (!parsed) {
43847
- addError(errors, `missing_frontmatter`, `SKILL.md must start with YAML frontmatter delimited by "---".`);
43848
- return { ok: false, errors, warnings };
43849
- }
43850
- const invalidFrontmatterLines = Object.keys(parsed.frontmatter).filter((key) => key.startsWith(`__invalid__`));
43851
- if (invalidFrontmatterLines.length > 0) {
43852
- addError(errors, `invalid_frontmatter`, `Frontmatter must contain simple "key: value" lines.`);
43853
- }
43854
- if (!parsed.name) {
43855
- addError(errors, `missing_name`, `Frontmatter field "name" is required.`);
43856
- } else if (!isValidSkillName(parsed.name)) {
43857
- addError(errors, `invalid_name`, `Skill name "${parsed.name}" must be kebab-case, under 64 characters, and path-safe.`);
43858
- }
43859
- if (!parsed.description) {
43860
- addError(errors, `missing_description`, `Frontmatter field "description" is required.`);
43861
- } else if (parsed.description.length > 500) {
43862
- addWarning(warnings, `long_description`, `Skill description is long; keep trigger metadata concise.`);
43863
- }
43864
- if (input.skillName && parsed.name && input.skillName !== parsed.name) {
43865
- addError(errors, `name_mismatch`, `Input skillName "${input.skillName}" must match frontmatter name "${parsed.name}".`);
43866
- }
43867
- if (input.description && parsed.description && input.description !== parsed.description) {
43868
- addError(errors, `description_mismatch`, `Input description must match frontmatter description.`);
43869
- }
43870
- if (!parsed.body) {
43871
- addError(errors, `empty_body`, `SKILL.md body is required.`);
43872
- } else {
43873
- if (!/(^|\n)#\s+\S/.test(parsed.body)) {
43874
- addWarning(warnings, `missing_title`, `Skill body should start with a Markdown H1 title.`);
43875
- }
43876
- for (const sectionPattern of REQUIRED_SECTIONS) {
43877
- if (!sectionPattern.test(parsed.body)) {
43878
- addWarning(warnings, `missing_recommended_section`, `Skill body should include when-to-use and execution workflow sections.`);
43879
- break;
43880
- }
43881
- }
43882
- }
43883
- if (input.baseDir) {
43884
- const normalizedBase = import_node_path12.default.resolve(input.baseDir);
43885
- for (const match2 of parsed.body.matchAll(/\]\(([^)]+)\)/g)) {
43886
- const href = match2[1].trim();
43887
- if (!href || /^[a-z][a-z0-9+.-]*:/i.test(href) || href.startsWith(`#`)) continue;
43888
- const target = import_node_path12.default.resolve(normalizedBase, href.split(`#`)[0]);
43889
- if (!target.startsWith(normalizedBase + import_node_path12.default.sep) && target !== normalizedBase) {
43890
- addError(errors, `unsafe_reference`, `Relative link "${href}" escapes the skill directory.`);
43891
- } else if (!(0, import_node_fs3.existsSync)(target)) {
43892
- addError(errors, `missing_reference`, `Relative link "${href}" does not exist in the skill directory.`);
43893
- }
43894
- }
43895
- }
43896
- return {
43897
- ok: errors.length === 0,
43898
- skill: parsed,
43899
- errors,
43900
- warnings
43901
- };
43902
- };
43903
- var mergeIssues = (target, source) => {
43904
- target.errors.push(...source.errors);
43905
- target.warnings.push(...source.warnings);
43906
- };
43907
- var validateScriptsDirectory = (skillDir, warnings) => {
43908
- const scriptsDir = import_node_path12.default.join(skillDir, `scripts`);
43909
- if (!(0, import_node_fs3.existsSync)(scriptsDir)) return;
43910
- const entries = (0, import_node_fs3.readdirSync)(scriptsDir, { withFileTypes: true }).filter((entry) => entry.isFile());
43911
- if (entries.length === 0) {
43912
- addWarning(warnings, `empty_scripts_dir`, `scripts/ exists but contains no files.`);
43913
- }
43914
- };
43915
- var validateSkillDirectory = (skillDir, options = {}) => {
43916
- const errors = [];
43917
- const warnings = [];
43918
- const normalizedSkillDir = import_node_path12.default.resolve(skillDir);
43919
- const skillMdPath = import_node_path12.default.join(normalizedSkillDir, `SKILL.md`);
43920
- if (!(0, import_node_fs3.existsSync)(normalizedSkillDir) || !(0, import_node_fs3.statSync)(normalizedSkillDir).isDirectory()) {
43921
- addError(errors, `missing_skill_dir`, `Skill directory does not exist: ${normalizedSkillDir}`);
43922
- return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
43923
- }
43924
- if (!(0, import_node_fs3.existsSync)(skillMdPath) || !(0, import_node_fs3.statSync)(skillMdPath).isFile()) {
43925
- addError(errors, `missing_skill_md`, `Skill directory must contain SKILL.md.`);
43926
- return { ok: false, errors, warnings, skillDir: normalizedSkillDir, skillMdPath };
43927
- }
43928
- const raw2 = (0, import_node_fs3.readFileSync)(skillMdPath, `utf-8`);
43929
- const documentResult = validateSkillDocument({
43930
- skillName: options.expectedName ?? import_node_path12.default.basename(normalizedSkillDir),
43931
- skillMd: raw2,
43932
- baseDir: normalizedSkillDir
43933
- });
43934
- mergeIssues({ errors, warnings }, documentResult);
43935
- validateScriptsDirectory(normalizedSkillDir, warnings);
43936
- return {
43937
- ok: errors.length === 0,
43938
- skill: documentResult.skill,
43939
- errors,
43940
- warnings,
43941
- skillDir: normalizedSkillDir,
43942
- skillMdPath
43943
- };
43944
- };
43945
- var smokeTestSkillDirectory = (skillDir, options = {}) => {
43946
- const directoryResult = validateSkillDirectory(skillDir, options);
43947
- const errors = [...directoryResult.errors];
43948
- const warnings = [...directoryResult.warnings];
43949
- let detail;
43950
- if (directoryResult.ok && directoryResult.skill) {
43951
- detail = `Base directory for this skill: ${directoryResult.skillDir}
43952
-
43953
- ${directoryResult.skill.body}`;
43954
- if (!detail.includes(directoryResult.skillDir)) {
43955
- addError(errors, `smoke_missing_base_dir`, `Skill detail does not include its base directory.`);
44115
+ type: `string`,
44116
+ description: `\u53EF\u9009\uFF1A\u53EA\u67E5\u770B\u6307\u5B9A mailboxId \u7684\u56DE\u4FE1\uFF0C\u4E0D\u586B\u5219\u67E5\u770B\u6240\u6709\u56DE\u4FE1`
44117
+ }
44118
+ },
44119
+ required: []
44120
+ },
44121
+ async execute(input) {
44122
+ const db3 = createSqliteDB();
44123
+ const fromMember = input.from_member;
44124
+ let msgs;
44125
+ if (fromMember) {
44126
+ const stmt = db3.prepare(
44127
+ `SELECT id, to_mailbox_id as toMailboxId, from_mailbox_id as fromMailboxId, content, send_time as sendTime, status
44128
+ FROM mailbox
44129
+ WHERE to_mailbox_id = 'manager' AND from_mailbox_id = ? AND status in ('pending', 'processing')
44130
+ ORDER BY send_time ASC`
44131
+ );
44132
+ msgs = stmt.all(fromMember);
44133
+ } else {
44134
+ const stmt = db3.prepare(
44135
+ `SELECT id, to_mailbox_id as toMailboxId, from_mailbox_id as fromMailboxId, content, send_time as sendTime, status
44136
+ FROM mailbox
44137
+ WHERE to_mailbox_id = 'manager' AND status in ('pending', 'processing')
44138
+ ORDER BY send_time ASC`
44139
+ );
44140
+ msgs = stmt.all();
43956
44141
  }
43957
- if (!detail.includes(directoryResult.skill.body)) {
43958
- addError(errors, `smoke_missing_body`, `Skill detail does not include the SKILL.md body.`);
44142
+ if (msgs.length === 0) {
44143
+ return [
44144
+ `[checkDepartmentReplies] \u56E2\u961F\u8FD9\u4F1A\u513F\u8FD8\u6CA1\u56DE\u4FE1\uFF0C\u591A\u534A\u662F\u8D1F\u8D23\u4EBA\u4ECD\u5728\u5904\u7406\uFF0C\u8FC7\u4F1A\u513F\u518D\u6765\u770B\u3002`,
44145
+ ``,
44146
+ `\u51E0\u53E5\u63D0\u9192\uFF1A`,
44147
+ `- \u8FD9\u4E0D\u4EE3\u8868\u8981\u4F60\u4EB2\u81EA\u4E0B\u573A read/bash/\u6D4B\u8BD5/\u6539\u4EE3\u7801\u2014\u2014\u56E2\u961F\u6CA1\u56DE\uFF0C\u4E0D\u662F\u4F60\u8BE5\u63A5\u624B\u7684\u4FE1\u53F7\u3002`,
44148
+ `- \u8001\u677F\u8981\u662F\u6B63\u7B49\u7740\uFF0C\u5148\u7528 send_message \u5982\u5B9E\u8DDF\u4ED6\u8BF4\u4E00\u53E5\u201C\u8D1F\u8D23\u4EBA\u8FD8\u5728\u5904\u7406\uFF0C\u6211\u62FF\u5230\u7ED3\u679C\u5C31\u540C\u6B65\u201D\u3002`,
44149
+ `- \u5982\u679C\u4EFB\u52A1\u521A\u6D3E\u51FA\u53BB\uFF0C\u4E0D\u8981\u5728\u540C\u4E00\u8F6E\u9A6C\u4E0A\u50AC\u529E\uFF1B\u7ED9\u8D1F\u8D23\u4EBA\u5408\u7406\u5904\u7406\u65F6\u95F4\u3002`,
44150
+ `- \u5DF2\u7ECF\u7B49\u4E86\u4E00\u9635\u3001\u8001\u677F\u53C8\u8FFD\u95EE\uFF0C\u6216\u53D1\u73B0\u660E\u786E\u98CE\u9669\u65F6\uFF0C\u518D\u7528 mailbox_followup \u6216 department_communicate \u53BB\u95EE\u5BF9\u5E94 Department Head \u771F\u5B9E\u8FDB\u5C55\u3001\u5361\u5728\u54EA\u3001\u5927\u6982\u4EC0\u4E48\u65F6\u5019\u597D\u3002`,
44151
+ `- \u522B\u5728\u8FD9\u4E00\u8F6E\u91CC\u53CD\u590D\u7A7A\u67E5 check_department_replies\uFF1B\u7B49\u56E2\u961F\u56DE\u4FE1\u3001\u8001\u677F\u8FFD\u95EE\u6216\u50AC\u529E\u6709\u4E86\u56DE\u97F3\uFF0C\u518D\u6765\u770B\u3002`
44152
+ ].join("\n");
43959
44153
  }
43960
- if (detail.length < 80) {
43961
- addWarning(warnings, `smoke_short_detail`, `Skill detail is unusually short.`);
44154
+ const updateStmt = db3.prepare(`UPDATE mailbox SET status = 'read' WHERE id = ?`);
44155
+ for (const msg of msgs) {
44156
+ updateStmt.run(msg.id);
43962
44157
  }
43963
- }
43964
- return {
43965
- ...directoryResult,
43966
- ok: errors.length === 0,
43967
- errors,
43968
- warnings,
43969
- detail
43970
- };
43971
- };
43972
- var formatSkillValidationIssues = (result) => {
43973
- const lines = [
43974
- ...result.errors.map((issue) => `- [${issue.code}] ${issue.message}`),
43975
- ...result.warnings.map((issue) => `- [warning:${issue.code}] ${issue.message}`)
43976
- ];
43977
- return lines.join(`
43978
- `);
43979
- };
44158
+ const replies = msgs.map((msg, i) => {
44159
+ const time = new Date(msg.sendTime).toLocaleString("zh-CN", { timeZone: "Asia/Shanghai" });
44160
+ return `--- \u56DE\u4FE1 ${i + 1} ---
44161
+ id: ${msg.id}
44162
+ \u6765\u81EA: ${msg.fromMailboxId}
44163
+ \u65F6\u95F4: ${time}
44164
+ \u72B6\u6001: ${msg.status}
44165
+ \u5185\u5BB9:
44166
+ ${msg.content}`;
44167
+ }).join("\n\n");
44168
+ return `[checkDepartmentReplies] \u6536\u5230 ${msgs.length} \u6761\u90E8\u95E8\u56DE\u4FE1\uFF1A
43980
44169
 
43981
- // src/department/learning.ts
43982
- var ensureDir = (dir) => (0, import_node_fs4.mkdirSync)(dir, { recursive: true });
43983
- var readJsonArray = (filePath) => {
43984
- if (!(0, import_node_fs4.existsSync)(filePath)) return [];
43985
- return JSON.parse((0, import_node_fs4.readFileSync)(filePath, "utf-8"));
43986
- };
43987
- var writeJsonArray = (filePath, records) => {
43988
- ensureDir(import_node_path13.default.dirname(filePath));
43989
- (0, import_node_fs4.writeFileSync)(filePath, JSON.stringify(records, null, " "), "utf-8");
43990
- };
43991
- var departmentMemoryPath = (departmentName) => {
43992
- return import_node_path13.default.join(getDepartmentWorkSpaceDir(departmentName), "department-memory.json");
43993
- };
43994
- var departmentSkillPath = (departmentName) => {
43995
- return import_node_path13.default.join(getDepartmentWorkSpaceDir(departmentName), "department-skills.json");
43996
- };
43997
- var departmentSkillDir = (departmentName, skillName) => {
43998
- return assertSafeSkillTarget(import_node_path13.default.join(getDepartmentWorkSpaceDir(departmentName), "skills"), skillName);
43999
- };
44000
- var proposalsPath = () => {
44001
- return import_node_path13.default.join(getDepartmentBaseDir(), "department-proposals.json");
44002
- };
44003
- var getDepartmentNameForHead = (request) => {
44004
- const mailboxId = request?.departmentAgentId;
44005
- if (!mailboxId) return null;
44006
- const member = getDepartmentMemberByMailboxId(mailboxId);
44007
- if (!member || member.role !== "department_head") return null;
44008
- const [departmentName] = mailboxId.split("::");
44009
- return departmentName || null;
44010
- };
44011
- var listDepartmentMemories = (departmentName) => {
44012
- return readJsonArray(departmentMemoryPath(departmentName)).sort((a, b) => b.updatedAt - a.updatedAt);
44013
- };
44014
- var createDepartmentMemory = (departmentName, input) => {
44015
- const now = Date.now();
44016
- const memory = {
44017
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44018
- departmentName,
44019
- title: input.title,
44020
- content: input.content,
44021
- sourceMailboxId: input.sourceMailboxId,
44022
- createdAt: now,
44023
- updatedAt: now
44024
- };
44025
- const records = listDepartmentMemories(departmentName);
44026
- records.push(memory);
44027
- writeJsonArray(departmentMemoryPath(departmentName), records);
44028
- return memory;
44029
- };
44030
- var updateDepartmentMemory = (departmentName, id, patch) => {
44031
- const records = listDepartmentMemories(departmentName);
44032
- const idx = records.findIndex((record) => record.id === id);
44033
- if (idx < 0) return null;
44034
- records[idx] = {
44035
- ...records[idx],
44036
- ...patch.title !== void 0 ? { title: patch.title } : {},
44037
- ...patch.content !== void 0 ? { content: patch.content } : {},
44038
- updatedAt: Date.now()
44039
- };
44040
- writeJsonArray(departmentMemoryPath(departmentName), records);
44041
- return records[idx];
44042
- };
44043
- var deleteDepartmentMemory = (departmentName, id) => {
44044
- const records = listDepartmentMemories(departmentName);
44045
- const target = records.find((record) => record.id === id) ?? null;
44046
- writeJsonArray(departmentMemoryPath(departmentName), records.filter((record) => record.id !== id));
44047
- return target;
44048
- };
44049
- var listDepartmentSkills = (departmentName) => {
44050
- return readJsonArray(departmentSkillPath(departmentName)).sort((a, b) => b.updatedAt - a.updatedAt);
44051
- };
44052
- var proposeDepartmentSkill = (departmentName, input) => {
44053
- const validation = validateSkillDocument({
44054
- skillName: input.skillName,
44055
- description: input.description,
44056
- skillMd: input.skillMd
44057
- });
44058
- if (!validation.ok) {
44059
- throw new Error(`[departmentSkill] Skill \u8349\u7A3F\u6821\u9A8C\u5931\u8D25\uFF1A
44060
- ${formatSkillValidationIssues(validation)}`);
44061
- }
44062
- const records = listDepartmentSkills(departmentName);
44063
- if (records.some((record) => record.skillName === input.skillName && record.status !== "dropped")) {
44064
- return null;
44065
- }
44066
- const now = Date.now();
44067
- const skill = {
44068
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44069
- departmentName,
44070
- skillName: input.skillName,
44071
- description: input.description,
44072
- skillMd: input.skillMd,
44073
- status: "pending",
44074
- createdByMailboxId: input.createdByMailboxId,
44075
- createdAt: now,
44076
- updatedAt: now
44077
- };
44078
- records.push(skill);
44079
- writeJsonArray(departmentSkillPath(departmentName), records);
44080
- return skill;
44081
- };
44082
- var keepDepartmentSkill = (departmentName, id) => {
44083
- const records = listDepartmentSkills(departmentName);
44084
- const idx = records.findIndex((record) => record.id === id);
44085
- if (idx < 0) return null;
44086
- records[idx] = { ...records[idx], status: "active", updatedAt: Date.now() };
44087
- const skillDir = departmentSkillDir(departmentName, records[idx].skillName);
44088
- const validation = validateSkillDocument({
44089
- skillName: records[idx].skillName,
44090
- description: records[idx].description,
44091
- skillMd: records[idx].skillMd,
44092
- baseDir: skillDir
44093
- });
44094
- if (!validation.ok) {
44095
- throw new Error(`[departmentSkill] Skill \u8349\u7A3F\u6821\u9A8C\u5931\u8D25\uFF0C\u62D2\u7EDD\u4FDD\u7559\uFF1A
44096
- ${formatSkillValidationIssues(validation)}`);
44097
- }
44098
- ensureDir(skillDir);
44099
- (0, import_node_fs4.writeFileSync)(import_node_path13.default.join(skillDir, "SKILL.md"), records[idx].skillMd, "utf-8");
44100
- const smokeTest = smokeTestSkillDirectory(skillDir, { expectedName: records[idx].skillName });
44101
- if (!smokeTest.ok) {
44102
- throw new Error(`[departmentSkill] Skill smoke test \u5931\u8D25\uFF1A
44103
- ${formatSkillValidationIssues(smokeTest)}`);
44170
+ ${replies}`;
44104
44171
  }
44105
- writeJsonArray(departmentSkillPath(departmentName), records);
44106
- return records[idx];
44107
- };
44108
- var dropDepartmentSkill = (departmentName, id) => {
44109
- const records = listDepartmentSkills(departmentName);
44110
- const idx = records.findIndex((record) => record.id === id);
44111
- if (idx < 0) return null;
44112
- records[idx] = { ...records[idx], status: "dropped", updatedAt: Date.now() };
44113
- writeJsonArray(departmentSkillPath(departmentName), records);
44114
- return records[idx];
44115
- };
44116
- var createDepartmentProposal = (input) => {
44117
- const records = readJsonArray(proposalsPath());
44118
- const proposal = {
44119
- ...input,
44120
- id: (0, import_node_crypto10.randomUUID)().slice(0, 8),
44121
- status: "pending",
44122
- createdAt: Date.now()
44123
- };
44124
- records.push(proposal);
44125
- writeJsonArray(proposalsPath(), records);
44126
- return proposal;
44127
- };
44128
- var buildDepartmentLearningContext = (departmentName) => {
44129
- if (!departmentName) return "";
44130
- const memories = listDepartmentMemories(departmentName);
44131
- const activeSkills = listDepartmentSkills(departmentName).filter((skill) => skill.status === "active");
44132
- if (memories.length === 0 && activeSkills.length === 0) return "";
44133
- const memoryLines = memories.map(
44134
- (memory) => ` - [id=${memory.id}] ${memory.title}
44135
- ${memory.content.replace(/\n/g, "\n ")}`
44136
- ).join("\n");
44137
- const skillLines = activeSkills.map(
44138
- (skill) => ` - ${skill.skillName}: ${skill.description}`
44139
- ).join("\n");
44140
- return [
44141
- `<department-learning-context department="${departmentName}">`,
44142
- memories.length > 0 ? `Department memories:
44143
- ${memoryLines}` : "",
44144
- activeSkills.length > 0 ? `Department skills:
44145
- ${skillLines}` : "",
44146
- `</department-learning-context>`
44147
- ].filter(Boolean).join("\n");
44148
44172
  };
44149
44173
 
44150
44174
  // src/tools/tools/department/DepartmentLearning.ts
@@ -44508,6 +44532,7 @@ var MAX_SESSION_OUTPUT_LENGTH = 5e4;
44508
44532
  var DEFAULT_TIMEOUT_MS = 3e4;
44509
44533
  var DEFAULT_SESSION_TTL_MS = 30 * 60 * 1e3;
44510
44534
  var SHELL_CANDIDATES2 = ["/bin/sh", "/usr/bin/sh", "/bin/bash"];
44535
+ var DEFAULT_PROTECTED_PORTS = ["3100"];
44511
44536
  var sessions = /* @__PURE__ */ new Map();
44512
44537
  function findExecutableShell2() {
44513
44538
  for (const shell of SHELL_CANDIDATES2) {
@@ -44538,6 +44563,64 @@ function validateCwd(cwd) {
44538
44563
  return `[bash] \u9519\u8BEF: cwd \u4E0D\u53EF\u7528: ${cwd} (${err.message})`;
44539
44564
  }
44540
44565
  }
44566
+ function getProtectedPorts() {
44567
+ const configured = process.env.DUCLAW_BASH_PROTECTED_PORTS ?? [process.env.KANBAN_PORT, process.env.PORT, ...DEFAULT_PROTECTED_PORTS].filter(Boolean).join(",");
44568
+ return [...new Set(
44569
+ configured.split(",").map((port) => port.trim()).filter((port) => /^\d{1,5}$/.test(port))
44570
+ )];
44571
+ }
44572
+ function validateProtectedRuntimeCommand(command) {
44573
+ const ports = getProtectedPorts();
44574
+ if (ports.length === 0) return null;
44575
+ for (const port of ports) {
44576
+ const portRef = new RegExp(`(^|[^\\d])(:${port}|-${port}\\b|${port}/tcp\\b)`);
44577
+ const targetsProtectedPort = portRef.test(command);
44578
+ if (!targetsProtectedPort) continue;
44579
+ const killsProcess = /\b(kill|pkill|killall|fuser)\b/.test(command) || /\blsof\b[\s\S]*\bxargs\b[\s\S]*\bkill\b/.test(command) || /\bss\b[\s\S]*\bxargs\b[\s\S]*\bkill\b/.test(command);
44580
+ if (killsProcess) {
44581
+ return [
44582
+ `[bash] \u62D2\u7EDD\u6267\u884C: \u547D\u4EE4\u8BD5\u56FE\u6E05\u7406 Duclaw runtime \u4FDD\u7559\u7AEF\u53E3 ${port}\u3002`,
44583
+ `\u8BE5\u7AEF\u53E3\u627F\u8F7D\u5F53\u524D\u667A\u80FD\u4F53\u7684 HTTP/gateway\uFF0C\u6740\u6389\u5B83\u4F1A\u5BFC\u81F4 iOS \u663E\u793A\u5DF2\u5173\u673A\u6216 502\u3002`,
44584
+ `\u8BF7\u6362\u7528\u9879\u76EE\u81EA\u5DF1\u7684\u5F00\u53D1\u7AEF\u53E3\uFF08\u4F8B\u5982 3001/5173\uFF09\uFF0C\u4E0D\u8981\u5BF9 ${port} \u6267\u884C kill/lsof/fuser/pkill\u3002`
44585
+ ].join("\n");
44586
+ }
44587
+ }
44588
+ if (/\b(pkill|killall)\b[\s\S]*\b(node|tsx|pnpm|npm)\b/.test(command)) {
44589
+ return [
44590
+ `[bash] \u62D2\u7EDD\u6267\u884C: \u547D\u4EE4\u4F1A\u6309\u8FDB\u7A0B\u540D\u6279\u91CF\u7EC8\u6B62 Node/JS \u8FDB\u7A0B\u3002`,
44591
+ `\u8FD9\u53EF\u80FD\u8BEF\u6740 Duclaw runtime\u3002\u8BF7\u53EA\u7EC8\u6B62\u660E\u786E\u5C5E\u4E8E\u5F53\u524D\u9879\u76EE\u7684 PID \u6216 session_id\u3002`
44592
+ ].join("\n");
44593
+ }
44594
+ return null;
44595
+ }
44596
+ function listProtectedRuntimeProcesses() {
44597
+ try {
44598
+ const output = (0, import_node_child_process.execFileSync)("ps", ["-eo", "pid=,ppid=,args="], { encoding: "utf8" });
44599
+ const currentPid = process.pid;
44600
+ return output.split("\n").map((line) => line.trim().match(/^(\d+)\s+(\d+)\s+(.*)$/)).filter((match2) => Boolean(match2)).map((match2) => ({
44601
+ pid: Number(match2[1]),
44602
+ command: match2[3] ?? ""
44603
+ })).filter(
44604
+ (processInfo) => Number.isFinite(processInfo.pid) && processInfo.pid !== currentPid && (processInfo.command.includes("duclaw-cli start") || processInfo.command.includes("duclaw-worker"))
44605
+ ).map(({ pid, command }) => ({ pid, command }));
44606
+ } catch {
44607
+ return [];
44608
+ }
44609
+ }
44610
+ function validateProtectedRuntimePidCommand(command) {
44611
+ if (!/\bkill\b/.test(command)) return null;
44612
+ const protectedProcesses = listProtectedRuntimeProcesses();
44613
+ for (const protectedProcess of protectedProcesses) {
44614
+ const pidRef = new RegExp(`(^|[^\\d])${protectedProcess.pid}([^\\d]|$)`);
44615
+ if (!pidRef.test(command)) continue;
44616
+ return [
44617
+ `[bash] \u62D2\u7EDD\u6267\u884C: \u547D\u4EE4\u8BD5\u56FE\u7EC8\u6B62 Duclaw runtime \u8FDB\u7A0B PID ${protectedProcess.pid}\u3002`,
44618
+ `\u5339\u914D\u8FDB\u7A0B: ${protectedProcess.command}`,
44619
+ `\u8BF7\u53EA\u7EC8\u6B62\u660E\u786E\u5C5E\u4E8E\u5F53\u524D\u9879\u76EE\u7684\u5F00\u53D1\u670D\u52A1 PID\uFF0C\u4E0D\u8981\u6740 duclaw-cli start \u6216 duclaw-worker\u3002`
44620
+ ].join("\n");
44621
+ }
44622
+ return null;
44623
+ }
44541
44624
  function truncateOutput(output, limit = MAX_OUTPUT_LENGTH) {
44542
44625
  if (output.length <= limit) return output;
44543
44626
  return output.slice(0, limit) + `
@@ -44659,6 +44742,10 @@ var bashTool = {
44659
44742
  if (!command || command.trim() === "") {
44660
44743
  return `[bash] \u9519\u8BEF: command \u4E0D\u80FD\u4E3A\u7A7A`;
44661
44744
  }
44745
+ const protectedCommandError = validateProtectedRuntimeCommand(command);
44746
+ if (protectedCommandError) return protectedCommandError;
44747
+ const protectedRuntimePidError = validateProtectedRuntimePidCommand(command);
44748
+ if (protectedRuntimePidError) return protectedRuntimePidError;
44662
44749
  if (userRequest?.workspacePath && explicitCwd) {
44663
44750
  const rejection = validateWorkspacePath(explicitCwd, userRequest.workspacePath);
44664
44751
  if (rejection) return `[bash] ${rejection}`;
@@ -53467,7 +53554,13 @@ toolRoutes.get("/tools", (c) => {
53467
53554
  toolRoutes.get("/skills", (c) => {
53468
53555
  try {
53469
53556
  const skills = SkillRegistry_default();
53470
- return c.json(skills.map((s) => ({ name: s.name, description: s.description, deletable: !!s.deletable })));
53557
+ return c.json(skills.map((s) => ({
53558
+ name: s.name,
53559
+ description: s.description,
53560
+ deletable: !!s.deletable,
53561
+ scope: s.scope ?? (s.deletable ? "project" : "global"),
53562
+ departmentName: s.departmentName
53563
+ })));
53471
53564
  } catch (err) {
53472
53565
  return c.json({ error: err.message || "Failed to list skills" }, 500);
53473
53566
  }
@@ -53482,6 +53575,8 @@ toolRoutes.get("/skills/:name", (c) => {
53482
53575
  name: skill.name,
53483
53576
  description: skill.description,
53484
53577
  deletable: !!skill.deletable,
53578
+ scope: skill.scope ?? (skill.deletable ? "project" : "global"),
53579
+ departmentName: skill.departmentName,
53485
53580
  detail: skill.getDetail()
53486
53581
  });
53487
53582
  } catch (err) {
@@ -53504,7 +53599,7 @@ var systemRoutes = new Hono2();
53504
53599
  var startTime = Date.now();
53505
53600
  systemRoutes.get("/system/info", (c) => {
53506
53601
  return c.json({
53507
- version: true ? "1.9.7" : "unknown",
53602
+ version: true ? "1.9.9" : "unknown",
53508
53603
  uptime: Math.floor((Date.now() - startTime) / 1e3),
53509
53604
  env: process.env.NODE_ENV || "development",
53510
53605
  nodeVersion: process.version