openfox 2.0.58 → 2.0.60

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.
Files changed (40) hide show
  1. package/dist/agent-defaults/explorer.agent.md +4 -0
  2. package/dist/{branch.api-5DMEAIBI.js → branch.api-LAN42BA5.js} +3 -2
  3. package/dist/{chat-handler-W5D5LKKU.js → chat-handler-MBR6ZQTX.js} +7 -7
  4. package/dist/{chunk-LAWUUAN7.js → chunk-3JRYK5UG.js} +323 -80
  5. package/dist/{chunk-YMEZFRY2.js → chunk-43UFGATL.js} +5 -5
  6. package/dist/{chunk-BIWVYBIE.js → chunk-BGTEIA2S.js} +4 -3
  7. package/dist/{chunk-UDZNMYWN.js → chunk-DEWNWF2P.js} +519 -280
  8. package/dist/{chunk-CFLGVCYT.js → chunk-DOJKA4XO.js} +20 -9
  9. package/dist/{chunk-55VITQP3.js → chunk-MTBQQ5DN.js} +9 -9
  10. package/dist/{chunk-XR2XC7WM.js → chunk-NY6IGGKZ.js} +3 -3
  11. package/dist/{chunk-ADOXTORQ.js → chunk-PK2RMVMB.js} +2 -2
  12. package/dist/{chunk-CDRQJDSJ.js → chunk-WNSAJCWT.js} +3 -2
  13. package/dist/cli/dev.js +2 -2
  14. package/dist/cli/index.js +2 -2
  15. package/dist/{config-C2YDEF3N.js → config-GMQUPNBY.js} +2 -2
  16. package/dist/{manager-CZOVNTSN.js → manager-344C5JLZ.js} +3 -3
  17. package/dist/{orchestrator-Q47D6JXG.js → orchestrator-PIZJ42J2.js} +7 -7
  18. package/dist/package.json +4 -2
  19. package/dist/{permissions-QLHZUEGW.js → permissions-CGEK3DGZ.js} +21 -17
  20. package/dist/{processor-2YJ33KUH.js → processor-PKKTNKWY.js} +7 -7
  21. package/dist/{project-creator-7RBHP6K6.js → project-creator-IDW7RACH.js} +4 -4
  22. package/dist/{protocol-PSKVUhdN.d.ts → protocol-J7khRG62.d.ts} +2 -0
  23. package/dist/{provider-SNXYPSPH.js → provider-LBYBC5AG.js} +3 -3
  24. package/dist/{pwa-M6QZ52ZO.js → pwa-T6JRAYJB.js} +11 -10
  25. package/dist/{serve-LOPGRMFP.js → serve-GKS55Y2C.js} +9 -9
  26. package/dist/server/index.d.ts +1 -1
  27. package/dist/server/index.js +8 -8
  28. package/dist/{server-XUDZRBD3.js → server-LMH7ZH5X.js} +8 -8
  29. package/dist/{service-YYJ5Y7CN.js → service-6DL3MJZD.js} +7 -5
  30. package/dist/{settings-X43DCM55.js → settings-2TQA7VTP.js} +2 -2
  31. package/dist/shared/index.d.ts +2 -2
  32. package/dist/skill-defaults/{browser.skill.md → browser/SKILL.md} +5 -3
  33. package/dist/{store-NS2RMRA7.js → store-FXTCHBI4.js} +2 -2
  34. package/dist/{tools-YJLXW3QS.js → tools-WGAZKL56.js} +6 -6
  35. package/dist/web/assets/index-C0YKw2cD.js +302 -0
  36. package/dist/web/assets/{index-DjfRXOO7.css → index-D4bVx84a.css} +1 -1
  37. package/dist/web/index.html +2 -2
  38. package/dist/web/sw.js +1 -1
  39. package/package.json +4 -2
  40. package/dist/web/assets/index-Dqqp_j-f.js +0 -302
@@ -9,12 +9,12 @@ import {
9
9
  getSessionProcesses,
10
10
  startProcessCommand,
11
11
  stopProcess
12
- } from "./chunk-BIWVYBIE.js";
12
+ } from "./chunk-BGTEIA2S.js";
13
13
  import {
14
14
  getMaxPerSession,
15
15
  getSessionProcessCount,
16
16
  terminateProcessTree
17
- } from "./chunk-ADOXTORQ.js";
17
+ } from "./chunk-PK2RMVMB.js";
18
18
  import {
19
19
  createMcpTools
20
20
  } from "./chunk-NWO6GRYE.js";
@@ -32,8 +32,9 @@ import {
32
32
  import {
33
33
  SETTINGS_KEYS,
34
34
  deleteSetting,
35
- getSetting
36
- } from "./chunk-CDRQJDSJ.js";
35
+ getSetting,
36
+ setSetting
37
+ } from "./chunk-WNSAJCWT.js";
37
38
  import {
38
39
  getProject
39
40
  } from "./chunk-YQ3SOPBI.js";
@@ -73,7 +74,7 @@ import {
73
74
  import {
74
75
  loadGlobalConfig,
75
76
  saveGlobalConfig
76
- } from "./chunk-YMEZFRY2.js";
77
+ } from "./chunk-43UFGATL.js";
77
78
  import {
78
79
  getGlobalConfigDir
79
80
  } from "./chunk-CQGTEGKL.js";
@@ -222,6 +223,9 @@ async function isPathWithinSandbox(path, workdir, sessionId) {
222
223
  function looksLikeRegex(str) {
223
224
  return /[*?+[\]\\]/.test(str);
224
225
  }
226
+ function isPlaceholderToken(str) {
227
+ return str.includes("__URL__") || str.includes("__FILEURL__") || str.includes("__SED__");
228
+ }
225
229
  function extractAbsolutePathsFromCommand(command) {
226
230
  if (!command.trim()) {
227
231
  return [];
@@ -273,16 +277,13 @@ function extractAbsolutePathsFromCommand(command) {
273
277
  }
274
278
  }
275
279
  }
276
- const absolutePattern = /(?:^|[\s=(])(\/?\/[a-zA-Z0-9_\-./]+)/g;
280
+ const absolutePattern = /(?:^|[\s=(])(\/[^\s"'|&;<>`()]+)/g;
277
281
  while ((match = absolutePattern.exec(sanitized)) !== null) {
278
- const pathCandidate = match[1];
279
- if (pathCandidate.includes("__URL__") || pathCandidate.includes("__FILEURL__")) {
280
- continue;
281
- }
282
- if (looksLikeRegex(pathCandidate)) {
283
- continue;
284
- }
285
- const resolved = normalize(pathCandidate);
282
+ const candidate = match[1];
283
+ if (isPlaceholderToken(candidate)) continue;
284
+ if (looksLikeRegex(candidate)) continue;
285
+ const resolved = normalize(candidate);
286
+ if (resolved === "/" || resolved === "") continue;
286
287
  if (!isSafePath(resolved)) {
287
288
  paths.push(resolved);
288
289
  }
@@ -1330,6 +1331,7 @@ function spawnShellProcess(command, cwd, _signal, detached = false) {
1330
1331
  cwd,
1331
1332
  env: { ...process.env, FORCE_COLOR: "0" },
1332
1333
  stdio: ["ignore", "pipe", "pipe"],
1334
+ windowsHide: true,
1333
1335
  ...detached ? { detached: true } : {}
1334
1336
  });
1335
1337
  }
@@ -1667,6 +1669,10 @@ ${Object.entries(schema.fields).map(([f, t]) => ` ${f}: ${t}`).join("\n")}`
1667
1669
  }
1668
1670
  );
1669
1671
 
1672
+ // src/server/sub-agents/manager.ts
1673
+ import { readFile as readFile8, access as access4 } from "fs/promises";
1674
+ import { join as join7, dirname as dirname5, isAbsolute as isAbsolute3 } from "path";
1675
+
1670
1676
  // src/server/agents/registry.ts
1671
1677
  import { readdir as readdir3, readFile as readFile5, writeFile as writeFile4, mkdir as mkdir3, access as access2, unlink as unlink2 } from "fs/promises";
1672
1678
  import { join as join4, dirname as dirname2 } from "path";
@@ -2029,62 +2035,173 @@ async function fileExists(path) {
2029
2035
  }
2030
2036
 
2031
2037
  // src/server/skills/registry.ts
2032
- import { writeFile as writeFile5, mkdir as mkdir4, unlink as unlink3 } from "fs/promises";
2038
+ import { writeFile as writeFile5, mkdir as mkdir4, unlink as unlink3, readdir as readdir4, readFile as readFile7, realpath as realpath2, rm } from "fs/promises";
2033
2039
  import { join as join6, dirname as dirname4 } from "path";
2034
2040
  import { fileURLToPath as fileURLToPath2 } from "url";
2041
+ import { homedir as homedir2 } from "os";
2035
2042
  import matter3 from "gray-matter";
2036
2043
  var __bundleDir2 = dirname4(fileURLToPath2(import.meta.url));
2037
2044
  var DEFAULTS_DIR2 = join6(__bundleDir2, "defaults");
2038
2045
  var DEFAULTS_DIR_ALT2 = join6(__bundleDir2, "skill-defaults");
2039
2046
  var SKILL_EXTENSION = ".skill.md";
2040
2047
  var SKILL_SETTING_PREFIX = "skill.enabled.";
2048
+ var PORTABLE_NAME_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2041
2049
  function getSkillsDir(configDir) {
2042
2050
  return join6(configDir, "skills");
2043
2051
  }
2044
2052
  function getProjectSkillsDir(projectDir) {
2045
2053
  return join6(projectDir, ".openfox", "skills");
2046
2054
  }
2047
- async function loadDefaultSkills() {
2048
- let defaults = await loadItemsFromDir(DEFAULTS_DIR2, {
2049
- extension: SKILL_EXTENSION,
2050
- logName: "skill"
2051
- });
2052
- if (!defaults.length) {
2053
- defaults = await loadItemsFromDir(DEFAULTS_DIR_ALT2, {
2054
- extension: SKILL_EXTENSION,
2055
- logName: "skill"
2056
- });
2055
+ function portableDisplayName(data, id) {
2056
+ const metadata = data["metadata"];
2057
+ if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) return id;
2058
+ const openfox = metadata["openfox"];
2059
+ if (!openfox || typeof openfox !== "object" || Array.isArray(openfox)) return id;
2060
+ const displayName = openfox["displayName"];
2061
+ return typeof displayName === "string" && displayName.trim() ? displayName : id;
2062
+ }
2063
+ function portableVersion(data) {
2064
+ const metadata = data["metadata"];
2065
+ if (metadata && typeof metadata === "object" && !Array.isArray(metadata)) {
2066
+ const version = metadata["version"];
2067
+ if (version !== void 0) return String(version);
2057
2068
  }
2058
- return defaults;
2069
+ return data["version"] === void 0 ? "" : String(data["version"]);
2070
+ }
2071
+ async function loadPortableSkills(dir, source) {
2072
+ let entries;
2073
+ try {
2074
+ entries = await readdir4(dir, { withFileTypes: true });
2075
+ } catch {
2076
+ return [];
2077
+ }
2078
+ const skills = [];
2079
+ for (const entry of entries) {
2080
+ if (!entry.isDirectory() && !entry.isSymbolicLink()) continue;
2081
+ const packageDir = join6(dir, entry.name);
2082
+ const entrypoint = join6(packageDir, "SKILL.md");
2083
+ try {
2084
+ const content = await readFile7(entrypoint, "utf-8");
2085
+ const parsed = matter3(content);
2086
+ const data = parsed.data;
2087
+ const id = typeof data["name"] === "string" ? data["name"].trim() : "";
2088
+ const description = typeof data["description"] === "string" ? data["description"].trim() : "";
2089
+ const prompt = parsed.content.trim();
2090
+ if (!id || !description || !prompt) continue;
2091
+ const resolvedDirectory = await realpath2(packageDir);
2092
+ const warnings = [];
2093
+ if (id.length > 64 || !PORTABLE_NAME_REGEX.test(id)) {
2094
+ warnings.push("Skill name must use 1-64 lowercase letters, numbers, and single hyphens");
2095
+ }
2096
+ if (id !== entry.name) {
2097
+ warnings.push(`Skill name "${id}" does not match package directory "${entry.name}"`);
2098
+ }
2099
+ skills.push({
2100
+ metadata: {
2101
+ id,
2102
+ name: portableDisplayName(data, id),
2103
+ description,
2104
+ version: portableVersion(data)
2105
+ },
2106
+ prompt,
2107
+ rawMetadata: data,
2108
+ entrypoint,
2109
+ directory: resolvedDirectory,
2110
+ source,
2111
+ legacy: false,
2112
+ warnings
2113
+ });
2114
+ } catch {
2115
+ }
2116
+ }
2117
+ return skills;
2118
+ }
2119
+ async function annotateLegacySkills(skills, dir, source) {
2120
+ return Promise.all(
2121
+ skills.map(async (skill) => {
2122
+ const entrypoint = join6(dir, `${skill.metadata.id}${SKILL_EXTENSION}`);
2123
+ let resolvedEntrypoint = entrypoint;
2124
+ try {
2125
+ resolvedEntrypoint = await realpath2(entrypoint);
2126
+ } catch {
2127
+ }
2128
+ return {
2129
+ ...skill,
2130
+ rawMetadata: { ...skill.metadata },
2131
+ entrypoint: resolvedEntrypoint,
2132
+ directory: dirname4(resolvedEntrypoint),
2133
+ source,
2134
+ legacy: true,
2135
+ warnings: ["Legacy .skill.md format"]
2136
+ };
2137
+ })
2138
+ );
2139
+ }
2140
+ async function loadSkillsDirectory(dir, source) {
2141
+ const [legacyRaw, portable] = await Promise.all([
2142
+ loadItemsFromDir(dir, { extension: SKILL_EXTENSION, logName: "skill" }),
2143
+ loadPortableSkills(dir, source)
2144
+ ]);
2145
+ const legacy = await annotateLegacySkills(legacyRaw, dir, source);
2146
+ const merged = new Map(legacy.map((skill) => [skill.metadata.id, skill]));
2147
+ for (const skill of portable) merged.set(skill.metadata.id, skill);
2148
+ return [...merged.values()];
2149
+ }
2150
+ function getSelectedSkillDirectories() {
2151
+ const raw = getSetting("skills.directories");
2152
+ if (!raw) return [];
2153
+ try {
2154
+ const parsed = JSON.parse(raw);
2155
+ return Array.isArray(parsed) ? parsed.filter((value) => typeof value === "string") : [];
2156
+ } catch {
2157
+ return [];
2158
+ }
2159
+ }
2160
+ async function loadDefaultSkills() {
2161
+ const defaults = await loadSkillsDirectory(DEFAULTS_DIR2, "bundled");
2162
+ if (defaults.length) return defaults;
2163
+ return loadSkillsDirectory(DEFAULTS_DIR_ALT2, "bundled");
2059
2164
  }
2060
2165
  async function loadUserSkills(configDir) {
2061
- return loadItemsFromDir(getSkillsDir(configDir), {
2062
- extension: SKILL_EXTENSION,
2063
- logName: "skill"
2064
- });
2166
+ return loadSkillsDirectory(getSkillsDir(configDir), "global-openfox");
2065
2167
  }
2066
2168
  async function loadProjectSkills(projectDir) {
2067
- return loadItemsFromDir(getProjectSkillsDir(projectDir), {
2068
- extension: SKILL_EXTENSION,
2069
- logName: "skill"
2070
- });
2169
+ return loadSkillsDirectory(getProjectSkillsDir(projectDir), "project-openfox");
2071
2170
  }
2072
- async function loadAllSkills(configDir, projectDir) {
2073
- const [defaultSkills, userSkills] = await Promise.all([loadDefaultSkills(), loadUserSkills(configDir)]);
2171
+ async function loadAllSkills(configDir, projectDir, options = {}) {
2172
+ return (await loadAllSkillsWithDiagnostics(configDir, projectDir, options)).skills;
2173
+ }
2174
+ async function loadAllSkillsWithDiagnostics(configDir, projectDir, options = {}) {
2175
+ const home = options.homeDir ?? homedir2();
2176
+ const selected = (options.selectedDirectories ?? getSelectedSkillDirectories()).map(
2177
+ (dir) => dir === "~" ? home : dir.startsWith("~/") ? join6(home, dir.slice(2)) : dir
2178
+ );
2179
+ const locations = [
2180
+ loadDefaultSkills(),
2181
+ loadSkillsDirectory(join6(home, ".agents", "skills"), "global-shared"),
2182
+ loadUserSkills(configDir),
2183
+ ...selected.map((dir) => loadSkillsDirectory(dir, "selected")),
2184
+ ...projectDir ? [loadSkillsDirectory(join6(projectDir, ".agents", "skills"), "project-shared"), loadProjectSkills(projectDir)] : []
2185
+ ];
2186
+ const groups = await Promise.all(locations);
2074
2187
  const skillMap = /* @__PURE__ */ new Map();
2075
- for (const skill of defaultSkills) {
2076
- skillMap.set(skill.metadata.id, skill);
2077
- }
2078
- for (const skill of userSkills) {
2079
- skillMap.set(skill.metadata.id, skill);
2080
- }
2081
- if (projectDir) {
2082
- const projectSkills = await loadProjectSkills(projectDir);
2083
- for (const skill of projectSkills) {
2188
+ const diagnostics = [];
2189
+ for (const group of groups) {
2190
+ for (const skill of group) {
2191
+ const previous = skillMap.get(skill.metadata.id);
2192
+ if (previous) {
2193
+ if (previous.directory === skill.directory && previous.legacy === skill.legacy) {
2194
+ diagnostics.push(`Skill "${skill.metadata.id}" reached through multiple paths`);
2195
+ } else {
2196
+ diagnostics.push(
2197
+ `Skill "${skill.metadata.id}" from ${skill.source ?? "unknown"} overrides ${previous.source ?? "unknown"}`
2198
+ );
2199
+ }
2200
+ }
2084
2201
  skillMap.set(skill.metadata.id, skill);
2085
2202
  }
2086
2203
  }
2087
- return Array.from(skillMap.values());
2204
+ return { skills: Array.from(skillMap.values()), diagnostics };
2088
2205
  }
2089
2206
  async function getEnabledSkills(configDir, projectDir) {
2090
2207
  const all = await loadAllSkills(configDir, projectDir);
@@ -2099,10 +2216,12 @@ function isSkillEnabled(skillId) {
2099
2216
  if (value === null) return true;
2100
2217
  return value === "true";
2101
2218
  }
2219
+ function setSkillEnabled(skillId, enabled) {
2220
+ setSetting(`${SKILL_SETTING_PREFIX}${skillId}`, String(enabled));
2221
+ }
2102
2222
  async function getDefaultSkillIds() {
2103
- const ids = await getDefaultIds(DEFAULTS_DIR2, SKILL_EXTENSION);
2104
- if (ids.length) return ids;
2105
- return getDefaultIds(DEFAULTS_DIR_ALT2, SKILL_EXTENSION);
2223
+ const defaults = await loadDefaultSkills();
2224
+ return defaults.map((s) => s.metadata.id);
2106
2225
  }
2107
2226
  async function isDefaultSkill(skillId) {
2108
2227
  const defaultIds = await getDefaultSkillIds();
@@ -2113,26 +2232,104 @@ function findSkillById(skillId, skills) {
2113
2232
  }
2114
2233
  async function skillExists(configDir, skillId, projectDir) {
2115
2234
  if (await pathExists(join6(getSkillsDir(configDir), `${skillId}${SKILL_EXTENSION}`))) return true;
2235
+ if (await pathExists(join6(getSkillsDir(configDir), skillId, "SKILL.md"))) return true;
2116
2236
  if (projectDir && await pathExists(join6(getProjectSkillsDir(projectDir), `${skillId}${SKILL_EXTENSION}`)))
2117
2237
  return true;
2238
+ if (projectDir && await pathExists(join6(getProjectSkillsDir(projectDir), skillId, "SKILL.md"))) return true;
2118
2239
  return false;
2119
2240
  }
2120
- async function saveSkill(configDir, skill) {
2121
- const skillsDir = getSkillsDir(configDir);
2122
- if (!await pathExists(skillsDir)) {
2123
- await mkdir4(skillsDir, { recursive: true });
2241
+ function portableFrontmatter(skill) {
2242
+ const metadata = {};
2243
+ if (skill.metadata.version) metadata["version"] = skill.metadata.version;
2244
+ if (skill.metadata.name !== skill.metadata.id) {
2245
+ metadata["openfox"] = { displayName: skill.metadata.name };
2124
2246
  }
2125
- const filePath = join6(skillsDir, `${skill.metadata.id}${SKILL_EXTENSION}`);
2126
- const content = matter3.stringify(skill.prompt, skill.metadata);
2127
- await writeFile5(filePath, content, "utf-8");
2247
+ return {
2248
+ name: skill.metadata.id,
2249
+ description: skill.metadata.description,
2250
+ ...Object.keys(metadata).length ? { metadata } : {}
2251
+ };
2252
+ }
2253
+ async function savePortableSkill(dir, skill) {
2254
+ const packageDir = join6(dir, skill.metadata.id);
2255
+ await mkdir4(packageDir, { recursive: true });
2256
+ await writeFile5(join6(packageDir, "SKILL.md"), matter3.stringify(skill.prompt, portableFrontmatter(skill)), "utf-8");
2257
+ }
2258
+ async function saveSkill(configDir, skill) {
2259
+ await savePortableSkill(getSkillsDir(configDir), skill);
2128
2260
  }
2129
2261
  async function saveSkillToProject(projectDir, skill) {
2130
- await saveItemToDir(
2131
- getProjectSkillsDir(projectDir),
2132
- skill,
2133
- SKILL_EXTENSION,
2134
- (s) => matter3.stringify(s.prompt, s.metadata)
2135
- );
2262
+ await savePortableSkill(getProjectSkillsDir(projectDir), skill);
2263
+ }
2264
+ function asRecord(value) {
2265
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
2266
+ }
2267
+ function updatedPortableFrontmatter(existing, metadata) {
2268
+ const frontmatter = { ...existing.rawMetadata ?? {} };
2269
+ const portableMetadata = { ...asRecord(frontmatter["metadata"]) };
2270
+ const openfox = { ...asRecord(portableMetadata["openfox"]) };
2271
+ frontmatter["name"] = existing.metadata.id;
2272
+ frontmatter["description"] = metadata.description;
2273
+ if (metadata.version) portableMetadata["version"] = metadata.version;
2274
+ else delete portableMetadata["version"];
2275
+ if (metadata.name !== existing.metadata.id) openfox["displayName"] = metadata.name;
2276
+ else delete openfox["displayName"];
2277
+ if (Object.keys(openfox).length) portableMetadata["openfox"] = openfox;
2278
+ else delete portableMetadata["openfox"];
2279
+ if (Object.keys(portableMetadata).length) frontmatter["metadata"] = portableMetadata;
2280
+ else delete frontmatter["metadata"];
2281
+ return frontmatter;
2282
+ }
2283
+ function canModifySkill(existing) {
2284
+ if (existing.source === "global-openfox" || existing.source === "project-openfox") return true;
2285
+ return !existing.legacy && (existing.source === "global-shared" || existing.source === "selected" || existing.source === "project-shared");
2286
+ }
2287
+ async function updateOwnedSkill(existing, changes) {
2288
+ if (!existing.entrypoint || !canModifySkill(existing)) return null;
2289
+ const normalizedMetadata = {
2290
+ ...existing.metadata,
2291
+ ...changes.metadata ?? {},
2292
+ id: existing.metadata.id
2293
+ };
2294
+ if (!existing.legacy) {
2295
+ const frontmatter = updatedPortableFrontmatter(existing, normalizedMetadata);
2296
+ const updated2 = {
2297
+ ...existing,
2298
+ ...changes,
2299
+ metadata: normalizedMetadata,
2300
+ rawMetadata: frontmatter,
2301
+ prompt: changes.prompt ?? existing.prompt
2302
+ };
2303
+ await writeFile5(existing.entrypoint, matter3.stringify(updated2.prompt, frontmatter), "utf-8");
2304
+ return updated2;
2305
+ }
2306
+ const metadata = {
2307
+ ...existing.rawMetadata ?? existing.metadata,
2308
+ ...changes.metadata ?? {},
2309
+ id: existing.metadata.id
2310
+ };
2311
+ const updated = {
2312
+ ...existing,
2313
+ ...changes,
2314
+ metadata,
2315
+ rawMetadata: metadata,
2316
+ prompt: changes.prompt ?? existing.prompt
2317
+ };
2318
+ await writeFile5(existing.entrypoint, matter3.stringify(updated.prompt, metadata), "utf-8");
2319
+ return updated;
2320
+ }
2321
+ async function deleteOwnedSkill(existing) {
2322
+ if (!existing.entrypoint || !canModifySkill(existing)) {
2323
+ return { success: false, reason: "This skill is read-only" };
2324
+ }
2325
+ try {
2326
+ if (existing.legacy) await unlink3(existing.entrypoint);
2327
+ else await rm(dirname4(existing.entrypoint), { recursive: true });
2328
+ deleteSetting(`${SKILL_SETTING_PREFIX}${existing.metadata.id}`);
2329
+ return { success: true };
2330
+ } catch {
2331
+ return { success: false, reason: "Cannot delete this skill" };
2332
+ }
2136
2333
  }
2137
2334
  async function deleteSkill(configDir, skillId) {
2138
2335
  const isDefault = await isDefaultSkill(skillId);
@@ -2910,7 +3107,7 @@ async function describeImageFromDataUrl(dataUrl, visionModel, options) {
2910
3107
  import { createHash as createHash2 } from "crypto";
2911
3108
  async function loadVisionModelFromGlobalConfig() {
2912
3109
  try {
2913
- const { loadGlobalConfig: loadGlobalConfig2, getVisionFallback } = await import("./config-C2YDEF3N.js");
3110
+ const { loadGlobalConfig: loadGlobalConfig2, getVisionFallback } = await import("./config-GMQUPNBY.js");
2914
3111
  const runtimeConfig = getRuntimeConfig();
2915
3112
  const mode = runtimeConfig.mode ?? "production";
2916
3113
  const globalConfig = await loadGlobalConfig2(mode);
@@ -3246,6 +3443,33 @@ function getWindowOptions(sessionId) {
3246
3443
  const id = getCurrentContextWindowId(sessionId);
3247
3444
  return id ? { contextWindowId: id } : void 0;
3248
3445
  }
3446
+ async function loadGitIgnoreRules(workdir) {
3447
+ if (!isAbsolute3(workdir)) return "";
3448
+ let currentDir = workdir;
3449
+ while (true) {
3450
+ const gitignorePath = join7(currentDir, ".gitignore");
3451
+ try {
3452
+ await access4(gitignorePath);
3453
+ const content = await readFile8(gitignorePath, "utf-8");
3454
+ const trimmed = content.trim();
3455
+ if (!trimmed) return "";
3456
+ const lines = trimmed.split("\n");
3457
+ const capped = lines.slice(0, 100);
3458
+ if (capped.length < lines.length) capped.push("# ... truncated (too many patterns)");
3459
+ const final = capped.join("\n").slice(0, 4096);
3460
+ return `## Repository Exclusion Rules (.gitignore)
3461
+
3462
+ The following patterns from \`.gitignore\` should be excluded from file searches:
3463
+
3464
+ ${final}`;
3465
+ } catch {
3466
+ }
3467
+ const parentDir = dirname5(currentDir);
3468
+ if (parentDir === currentDir) break;
3469
+ currentDir = parentDir;
3470
+ }
3471
+ return "";
3472
+ }
3249
3473
  async function executeSubAgent(options) {
3250
3474
  const {
3251
3475
  subAgentType,
@@ -3317,7 +3541,9 @@ async function executeSubAgent(options) {
3317
3541
  const config = getRuntimeConfig();
3318
3542
  const configDir = getGlobalConfigDir(config.mode ?? "production");
3319
3543
  const skills = await getEnabledSkillMetadata(configDir, config.workdir);
3320
- const systemPrompt = buildBasePrompt(session.workdir, void 0, skills.length > 0 ? skills : void 0, llmClient.getModel()) + "\n\n" + agentDef.prompt + RETURN_VALUE_INSTRUCTION;
3544
+ const hasRunCommand = agentDef.metadata.allowedTools?.includes("run_command") ?? false;
3545
+ const gitignoreSection = hasRunCommand ? await loadGitIgnoreRules(session.workdir) : "";
3546
+ const systemPrompt = buildBasePrompt(session.workdir, void 0, skills.length > 0 ? skills : void 0, llmClient.getModel()) + "\n\n" + agentDef.prompt + (gitignoreSection ? "\n\n" + gitignoreSection : "") + RETURN_VALUE_INSTRUCTION;
3321
3547
  const subAgentScope = { type: "subagent", sessionId, subAgentId, subAgentType };
3322
3548
  const loopResult = await runTopLevelAgentLoop(
3323
3549
  {
@@ -3425,7 +3651,7 @@ var callSubAgentTool = {
3425
3651
  };
3426
3652
  }
3427
3653
  try {
3428
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-YJLXW3QS.js");
3654
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-WGAZKL56.js");
3429
3655
  const toolRegistry = getToolRegistryForAgent2(agentDef);
3430
3656
  const turnMetrics = new TurnMetrics();
3431
3657
  const result = await executeSubAgent({
@@ -3463,6 +3689,13 @@ var callSubAgentTool = {
3463
3689
  };
3464
3690
 
3465
3691
  // src/server/tools/load-skill.ts
3692
+ function formatSkillPrompt(skill) {
3693
+ if (skill.legacy !== false || !skill.directory) return skill.prompt;
3694
+ return `Skill package directory: ${skill.directory}
3695
+ Resolve relative paths in these instructions from that directory.
3696
+
3697
+ ${skill.prompt}`;
3698
+ }
3466
3699
  var handler = async (args, _context, helpers) => {
3467
3700
  const { skillId } = args;
3468
3701
  if (!skillId) {
@@ -3479,7 +3712,7 @@ var handler = async (args, _context, helpers) => {
3479
3712
  const available = allSkills.map((s) => s.metadata.id).join(", ");
3480
3713
  return helpers.error(`Skill "${skillId}" not found. Available skills: ${available}`);
3481
3714
  }
3482
- return helpers.success(skill.prompt);
3715
+ return helpers.success(formatSkillPrompt(skill));
3483
3716
  };
3484
3717
  var loadSkillTool = createTool(
3485
3718
  "load_skill",
@@ -3676,13 +3909,13 @@ var webFetchTool = createTool(
3676
3909
 
3677
3910
  // src/server/dev-server/manager.ts
3678
3911
  import { spawn as spawn2 } from "child_process";
3679
- import { readFile as readFile7, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
3680
- import { resolve as resolve5, join as join7 } from "path";
3912
+ import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir5 } from "fs/promises";
3913
+ import { resolve as resolve5, join as join8 } from "path";
3681
3914
  var MAX_LOG_LINES = 2e3;
3682
3915
  var MAX_LOG_BYTES = 1e5;
3683
3916
  var getDevServerConfigPath = (workdir) => {
3684
3917
  const mode = getRuntimeConfig().mode ?? "production";
3685
- return join7(resolve5(workdir), ".openfox", `${mode === "development" ? "dev-dev" : "dev"}.json`);
3918
+ return join8(resolve5(workdir), ".openfox", `${mode === "development" ? "dev-dev" : "dev"}.json`);
3686
3919
  };
3687
3920
  var ERROR_PATTERNS = [
3688
3921
  /EADDRINUSE/,
@@ -3743,7 +3976,7 @@ var DevServerManager = class {
3743
3976
  async loadConfig(workdir) {
3744
3977
  try {
3745
3978
  const configPath = getDevServerConfigPath(workdir);
3746
- const raw = await readFile7(configPath, "utf-8");
3979
+ const raw = await readFile9(configPath, "utf-8");
3747
3980
  const parsed = JSON.parse(raw);
3748
3981
  if (!parsed.command || !parsed.url) return null;
3749
3982
  return {
@@ -3758,7 +3991,7 @@ var DevServerManager = class {
3758
3991
  }
3759
3992
  async saveConfig(workdir, config) {
3760
3993
  const resolved = this.resolveWorkdir(workdir);
3761
- const dirPath = join7(resolved, ".openfox");
3994
+ const dirPath = join8(resolved, ".openfox");
3762
3995
  await mkdir5(dirPath, { recursive: true });
3763
3996
  const configPath = getDevServerConfigPath(workdir);
3764
3997
  await writeFile6(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
@@ -3793,7 +4026,8 @@ var DevServerManager = class {
3793
4026
  cwd: resolved,
3794
4027
  env: { ...process.env, FORCE_COLOR: "1" },
3795
4028
  stdio: ["ignore", "pipe", "pipe"],
3796
- detached: true
4029
+ detached: true,
4030
+ windowsHide: true
3797
4031
  });
3798
4032
  instance.process = proc;
3799
4033
  const appendLog = (stream, content) => {
@@ -4241,7 +4475,7 @@ function resolveAgentDef2(sessionManager, sessionId) {
4241
4475
  }
4242
4476
  async function buildCachedPrompt(sessionManager, sessionId, agentDef) {
4243
4477
  const { instructionContent, skills } = await loadSessionContext(sessionManager, sessionId);
4244
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-YJLXW3QS.js");
4478
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-WGAZKL56.js");
4245
4479
  const tools = getToolRegistryForAgent2(agentDef).definitions;
4246
4480
  const toolFingerprint = getToolFingerprint(tools);
4247
4481
  const allAgents = await loadAllAgentsDefault();
@@ -4254,7 +4488,7 @@ async function buildCachedPrompt(sessionManager, sessionId, agentDef) {
4254
4488
  async function computeSessionHash(sessionManager, sessionId) {
4255
4489
  const { instructionContent, skills } = await loadSessionContext(sessionManager, sessionId);
4256
4490
  const agentDef = await resolveAgentDef2(sessionManager, sessionId);
4257
- const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-YJLXW3QS.js");
4491
+ const { getToolRegistryForAgent: getToolRegistryForAgent2 } = await import("./tools-WGAZKL56.js");
4258
4492
  const tools = getToolRegistryForAgent2(agentDef).definitions;
4259
4493
  const toolFingerprint = getToolFingerprint(tools);
4260
4494
  return computeDynamicContextHash(instructionContent, skills, toolFingerprint);
@@ -4273,6 +4507,7 @@ async function applyDynamicContext(sessionManager, sessionId) {
4273
4507
  // src/server/tools/mcp-config.ts
4274
4508
  var mcpManagerForTools = null;
4275
4509
  var mcpConfigMode = "production";
4510
+ var mcpConfigPath;
4276
4511
  var mcpNotifyChanged = null;
4277
4512
  function setMcpManagerForTools(manager) {
4278
4513
  mcpManagerForTools = manager;
@@ -4280,6 +4515,9 @@ function setMcpManagerForTools(manager) {
4280
4515
  function setMcpConfigMode(mode) {
4281
4516
  mcpConfigMode = mode;
4282
4517
  }
4518
+ function setMcpConfigPath(path) {
4519
+ mcpConfigPath = path;
4520
+ }
4283
4521
  function setNotifyMcpServersChanged(fn) {
4284
4522
  mcpNotifyChanged = fn;
4285
4523
  }
@@ -4355,13 +4593,13 @@ var mcpConfigTool = createTool(
4355
4593
  return helpers.error("MCP manager not available");
4356
4594
  }
4357
4595
  async function persistAndRebuild(updater) {
4358
- const globalConfig = await loadGlobalConfig(mcpConfigMode);
4596
+ const globalConfig = await loadGlobalConfig(mcpConfigMode, mcpConfigPath);
4359
4597
  const mcpServers = { ...globalConfig.mcpServers ?? {} };
4360
4598
  const updated = updater(mcpServers);
4361
- await saveGlobalConfig(mcpConfigMode, { ...globalConfig, mcpServers: updated });
4599
+ await saveGlobalConfig(mcpConfigMode, { ...globalConfig, mcpServers: updated }, mcpConfigPath);
4362
4600
  }
4363
4601
  async function rebuildTools() {
4364
- const { setMcpTools: setMcpTools2 } = await import("./tools-YJLXW3QS.js");
4602
+ const { setMcpTools: setMcpTools2 } = await import("./tools-WGAZKL56.js");
4365
4603
  const mcpTools = createMcpTools(mcpManagerForTools);
4366
4604
  setMcpTools2(mcpTools);
4367
4605
  }
@@ -4463,7 +4701,7 @@ var mcpConfigTool = createTool(
4463
4701
  );
4464
4702
 
4465
4703
  // src/server/tools/trace-code.ts
4466
- import { stat as stat2, readFile as readFile8 } from "fs/promises";
4704
+ import { stat as stat2, readFile as readFile10 } from "fs/promises";
4467
4705
  function graphNode(location, depth, relation, symbolName, symbolKind) {
4468
4706
  const node = { location, depth, relation, symbolName };
4469
4707
  if (symbolKind) node.symbolKind = symbolKind;
@@ -4495,7 +4733,7 @@ async function getCachedLines(path, approvedPaths) {
4495
4733
  }
4496
4734
  try {
4497
4735
  const stats = await stat2(path);
4498
- const content = await readFile8(path, "utf-8");
4736
+ const content = await readFile10(path, "utf-8");
4499
4737
  const lines = content.split("\n");
4500
4738
  fileCache.set(path, { mtimeMs: stats.mtimeMs, lines });
4501
4739
  if (fileCache.size > CACHE_MAX_SIZE) {
@@ -4997,14 +5235,18 @@ export {
4997
5235
  loadUserSkills,
4998
5236
  loadProjectSkills,
4999
5237
  loadAllSkills,
5238
+ loadAllSkillsWithDiagnostics,
5000
5239
  getEnabledSkillMetadata,
5001
5240
  isSkillEnabled,
5241
+ setSkillEnabled,
5002
5242
  getDefaultSkillIds,
5003
5243
  isDefaultSkill,
5004
5244
  findSkillById,
5005
5245
  skillExists,
5006
5246
  saveSkill,
5007
5247
  saveSkillToProject,
5248
+ updateOwnedSkill,
5249
+ deleteOwnedSkill,
5008
5250
  deleteSkill,
5009
5251
  deleteProjectSkill,
5010
5252
  runTopLevelAgentLoop,
@@ -5021,6 +5263,7 @@ export {
5021
5263
  applyDynamicContext,
5022
5264
  setMcpManagerForTools,
5023
5265
  setMcpConfigMode,
5266
+ setMcpConfigPath,
5024
5267
  setNotifyMcpServersChanged,
5025
5268
  parseToolPermissions,
5026
5269
  getToolPermissions,
@@ -5031,4 +5274,4 @@ export {
5031
5274
  getToolRegistryForAgent,
5032
5275
  createToolRegistry
5033
5276
  };
5034
- //# sourceMappingURL=chunk-LAWUUAN7.js.map
5277
+ //# sourceMappingURL=chunk-3JRYK5UG.js.map
@@ -125,8 +125,8 @@ var configSchema = z.object({
125
125
  function getVisionFallback(config) {
126
126
  return config.visionFallback ?? getDefaultVisionFallback();
127
127
  }
128
- async function loadGlobalConfig(mode) {
129
- const configPath = getGlobalConfigPath(mode);
128
+ async function loadGlobalConfig(mode, configPathOverride) {
129
+ const configPath = configPathOverride ?? getGlobalConfigPath(mode);
130
130
  try {
131
131
  const content = await readFile(configPath, "utf-8");
132
132
  const parsed = JSON.parse(content);
@@ -138,8 +138,8 @@ async function loadGlobalConfig(mode) {
138
138
  function getDefaultVisionFallback() {
139
139
  return { enabled: false, url: "http://localhost:11434", model: "qwen3.5:0.8b", timeout: 120 };
140
140
  }
141
- async function saveGlobalConfig(mode, config) {
142
- const configPath = getGlobalConfigPath(mode);
141
+ async function saveGlobalConfig(mode, config, configPathOverride) {
142
+ const configPath = configPathOverride ?? getGlobalConfigPath(mode);
143
143
  const fullConfig = {
144
144
  providers: config.providers ?? [],
145
145
  mcpServers: config.mcpServers,
@@ -291,4 +291,4 @@ export {
291
291
  removeProvider,
292
292
  activateProvider
293
293
  };
294
- //# sourceMappingURL=chunk-YMEZFRY2.js.map
294
+ //# sourceMappingURL=chunk-43UFGATL.js.map