openfox 1.6.15 → 1.6.17

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.
@@ -6,7 +6,7 @@ import {
6
6
  createVerifierNudgeConfig,
7
7
  runBuilderTurn,
8
8
  runChatTurn
9
- } from "./chunk-YM6VHAPM.js";
9
+ } from "./chunk-RPPEIB3F.js";
10
10
  import {
11
11
  TurnMetrics,
12
12
  agentExists,
@@ -17,46 +17,37 @@ import {
17
17
  createMessageStartEvent,
18
18
  createToolRegistry,
19
19
  deleteAgent,
20
- deleteItem,
21
20
  deleteSkill,
22
21
  devServerManager,
23
- dirExists,
24
- ensureDefaultAgents,
25
- ensureDefaultSkills,
26
- ensureDir,
27
22
  executeSubAgent,
28
23
  findAgentById,
29
- findById,
30
- findModifiedDefaultFiles,
31
24
  findSkillById,
32
25
  getAllInstructions,
33
- getBundleDir,
26
+ getDefaultAgentContent,
34
27
  getDefaultAgentIds,
35
28
  getDefaultIds,
29
+ getDefaultSkillContent,
36
30
  getDefaultSkillIds,
37
31
  getEnabledSkillMetadata,
38
- getModifiedDefaultAgentIds,
39
- getModifiedDefaultSkillIds,
40
32
  getToolRegistryForAgent,
41
33
  isSkillEnabled,
42
34
  loadAllAgents,
43
35
  loadAllAgentsDefault,
44
36
  loadAllSkills,
45
- loadItems,
37
+ loadDefaultAgents,
38
+ loadDefaultSkills,
39
+ loadItemsFromDir,
40
+ loadUserAgents,
41
+ loadUserSkills,
46
42
  maybeAutoCompactContext,
43
+ pathExists,
47
44
  performManualContextCompaction,
48
- restoreAllDefaultAgents,
49
- restoreAllDefaultSkills,
50
- restoreDefault,
51
- restoreDefaultAgent,
52
- restoreDefaultSkill,
53
45
  saveAgent,
54
- saveItem,
55
46
  saveSkill,
56
47
  setSkillEnabled,
57
48
  skillExists,
58
49
  spawnShellProcess
59
- } from "./chunk-3WHZ47PY.js";
50
+ } from "./chunk-R7U3CD2U.js";
60
51
  import {
61
52
  getPathSeparator,
62
53
  getPlatformShell,
@@ -156,8 +147,8 @@ import {
156
147
  import express from "express";
157
148
  import cors from "cors";
158
149
  import { createServer as createHttpServer } from "http";
159
- import { fileURLToPath as fileURLToPath3 } from "url";
160
- import { dirname as dirname4, resolve, join as join5 } from "path";
150
+ import { fileURLToPath as fileURLToPath4 } from "url";
151
+ import { dirname as dirname5, resolve, join as join5 } from "path";
161
152
  import { readFile as readFile3 } from "fs/promises";
162
153
  import { createServer as createViteServer } from "vite";
163
154
 
@@ -1426,7 +1417,7 @@ function unsubscribeAllFromTerminal(ws) {
1426
1417
  }
1427
1418
 
1428
1419
  // src/server/workflows/registry.ts
1429
- import { readdir, readFile, writeFile, copyFile, mkdir, access, unlink } from "fs/promises";
1420
+ import { readdir, readFile, writeFile, mkdir, access, unlink } from "fs/promises";
1430
1421
  import { join, dirname } from "path";
1431
1422
  import { constants } from "fs";
1432
1423
  import { fileURLToPath } from "url";
@@ -1437,7 +1428,7 @@ var WORKFLOW_EXTENSION = ".workflow.json";
1437
1428
  function getWorkflowsDir(configDir) {
1438
1429
  return join(configDir, "workflows");
1439
1430
  }
1440
- async function pathExists(path) {
1431
+ async function pathExists2(path) {
1441
1432
  try {
1442
1433
  await access(path, constants.R_OK);
1443
1434
  return true;
@@ -1445,49 +1436,20 @@ async function pathExists(path) {
1445
1436
  return false;
1446
1437
  }
1447
1438
  }
1448
- async function ensureDefaultWorkflows(configDir) {
1449
- const workflowsDir = getWorkflowsDir(configDir);
1450
- if (!await pathExists(workflowsDir)) {
1451
- await mkdir(workflowsDir, { recursive: true });
1452
- }
1453
- let defaultFiles;
1454
- let sourceDir;
1455
- try {
1456
- defaultFiles = (await readdir(DEFAULTS_DIR)).filter((f) => f.endsWith(WORKFLOW_EXTENSION));
1457
- sourceDir = DEFAULTS_DIR;
1458
- } catch {
1459
- try {
1460
- defaultFiles = (await readdir(DEFAULTS_DIR_ALT)).filter((f) => f.endsWith(WORKFLOW_EXTENSION));
1461
- sourceDir = DEFAULTS_DIR_ALT;
1462
- } catch {
1463
- logger.warn("No bundled workflow defaults found", { dir: DEFAULTS_DIR });
1464
- return;
1465
- }
1466
- }
1467
- for (const file of defaultFiles) {
1468
- const targetPath = join(workflowsDir, file);
1469
- try {
1470
- await copyFile(join(sourceDir, file), targetPath);
1471
- } catch (err) {
1472
- logger.error("Failed to copy default workflow", { file, error: err instanceof Error ? err.message : String(err) });
1473
- }
1474
- }
1475
- }
1476
- async function loadAllWorkflows(configDir) {
1477
- const workflowsDir = getWorkflowsDir(configDir);
1478
- if (!await pathExists(workflowsDir)) {
1439
+ async function loadWorkflowsFromDir(dir) {
1440
+ if (!await pathExists2(dir)) {
1479
1441
  return [];
1480
1442
  }
1481
1443
  let files;
1482
1444
  try {
1483
- files = (await readdir(workflowsDir)).filter((f) => f.endsWith(WORKFLOW_EXTENSION));
1445
+ files = (await readdir(dir)).filter((f) => f.endsWith(WORKFLOW_EXTENSION));
1484
1446
  } catch {
1485
1447
  return [];
1486
1448
  }
1487
1449
  const workflows = [];
1488
1450
  for (const file of files) {
1489
1451
  try {
1490
- const raw = await readFile(join(workflowsDir, file), "utf-8");
1452
+ const raw = await readFile(join(dir, file), "utf-8");
1491
1453
  const parsed = JSON.parse(raw);
1492
1454
  if (parsed.metadata?.id && parsed.steps?.length > 0) {
1493
1455
  workflows.push(parsed);
@@ -1500,6 +1462,30 @@ async function loadAllWorkflows(configDir) {
1500
1462
  }
1501
1463
  return workflows;
1502
1464
  }
1465
+ async function loadDefaultWorkflows() {
1466
+ let defaults = await loadWorkflowsFromDir(DEFAULTS_DIR);
1467
+ if (!defaults.length) {
1468
+ defaults = await loadWorkflowsFromDir(DEFAULTS_DIR_ALT);
1469
+ }
1470
+ return defaults;
1471
+ }
1472
+ async function loadUserWorkflows(configDir) {
1473
+ return loadWorkflowsFromDir(getWorkflowsDir(configDir));
1474
+ }
1475
+ async function loadAllWorkflows(configDir) {
1476
+ const [defaultWorkflows, userWorkflows] = await Promise.all([
1477
+ loadDefaultWorkflows(),
1478
+ loadUserWorkflows(configDir)
1479
+ ]);
1480
+ const workflowMap = /* @__PURE__ */ new Map();
1481
+ for (const workflow of defaultWorkflows) {
1482
+ workflowMap.set(workflow.metadata.id, workflow);
1483
+ }
1484
+ for (const workflow of userWorkflows) {
1485
+ workflowMap.set(workflow.metadata.id, workflow);
1486
+ }
1487
+ return Array.from(workflowMap.values());
1488
+ }
1503
1489
  async function getDefaultWorkflowIds() {
1504
1490
  for (const dir of [DEFAULTS_DIR, DEFAULTS_DIR_ALT]) {
1505
1491
  try {
@@ -1510,56 +1496,40 @@ async function getDefaultWorkflowIds() {
1510
1496
  }
1511
1497
  return [];
1512
1498
  }
1513
- async function restoreDefaultWorkflow(configDir, workflowId) {
1514
- const filename = `${workflowId}${WORKFLOW_EXTENSION}`;
1515
- for (const dir of [DEFAULTS_DIR, DEFAULTS_DIR_ALT]) {
1516
- const sourcePath = join(dir, filename);
1517
- if (await pathExists(sourcePath)) {
1518
- const targetPath = join(getWorkflowsDir(configDir), filename);
1519
- await copyFile(sourcePath, targetPath);
1520
- return true;
1521
- }
1522
- }
1523
- return false;
1499
+ async function getDefaultWorkflowContent(workflowId) {
1500
+ const defaults = await loadDefaultWorkflows();
1501
+ return defaults.find((w) => w.metadata.id === workflowId) ?? null;
1524
1502
  }
1525
- async function getModifiedDefaultWorkflowIds(configDir) {
1526
- return findModifiedDefaultFiles(
1527
- await getDefaultWorkflowIds(),
1528
- WORKFLOW_EXTENSION,
1529
- [DEFAULTS_DIR, DEFAULTS_DIR_ALT],
1530
- getWorkflowsDir(configDir)
1531
- );
1532
- }
1533
- async function restoreAllDefaultWorkflows(configDir) {
1534
- const ids = await getDefaultWorkflowIds();
1535
- let count = 0;
1536
- for (const id of ids) {
1537
- if (await restoreDefaultWorkflow(configDir, id)) count++;
1538
- }
1539
- return count;
1503
+ async function isDefaultWorkflow(workflowId) {
1504
+ const defaultIds = await getDefaultWorkflowIds();
1505
+ return defaultIds.includes(workflowId);
1540
1506
  }
1541
1507
  function findWorkflowById(workflowId, workflows) {
1542
1508
  return workflows.find((p) => p.metadata.id === workflowId);
1543
1509
  }
1544
1510
  async function workflowExists(configDir, workflowId) {
1545
1511
  const filePath = join(getWorkflowsDir(configDir), `${workflowId}${WORKFLOW_EXTENSION}`);
1546
- return pathExists(filePath);
1512
+ return pathExists2(filePath);
1547
1513
  }
1548
1514
  async function saveWorkflow(configDir, workflow) {
1549
1515
  const workflowsDir = getWorkflowsDir(configDir);
1550
- if (!await pathExists(workflowsDir)) {
1516
+ if (!await pathExists2(workflowsDir)) {
1551
1517
  await mkdir(workflowsDir, { recursive: true });
1552
1518
  }
1553
1519
  const filePath = join(workflowsDir, `${workflow.metadata.id}${WORKFLOW_EXTENSION}`);
1554
1520
  await writeFile(filePath, JSON.stringify(workflow, null, 2) + "\n", "utf-8");
1555
1521
  }
1556
1522
  async function deleteWorkflow(configDir, workflowId) {
1523
+ const isDefault = await isDefaultWorkflow(workflowId);
1524
+ if (isDefault) {
1525
+ return { success: false, reason: "Cannot delete built-in defaults" };
1526
+ }
1557
1527
  const filePath = join(getWorkflowsDir(configDir), `${workflowId}${WORKFLOW_EXTENSION}`);
1558
1528
  try {
1559
1529
  await unlink(filePath);
1560
- return true;
1530
+ return { success: true };
1561
1531
  } catch {
1562
- return false;
1532
+ return { success: false };
1563
1533
  }
1564
1534
  }
1565
1535
 
@@ -4898,102 +4868,47 @@ ${summary}`,
4898
4868
  }
4899
4869
  };
4900
4870
 
4901
- // src/server/commands/registry.ts
4902
- import { readdir as readdir2 } from "fs/promises";
4903
- import { join as join4 } from "path";
4904
- var DEFAULTS_DIR2 = "defaults";
4905
- var DEFAULTS_DIR_ALT2 = "command-defaults";
4906
- var COMMAND_EXTENSION = ".command.md";
4907
- function getCommandsDir(configDir) {
4908
- return join4(configDir, "commands");
4909
- }
4910
- async function ensureDefaultCommands(configDir) {
4911
- const bundleDir = getBundleDir();
4912
- const commandsDir = getCommandsDir(configDir);
4913
- await ensureDir(commandsDir);
4914
- const ids = await getDefaultIds(bundleDir, DEFAULTS_DIR2, COMMAND_EXTENSION);
4915
- for (const id of ids) {
4916
- if (!await restoreDefault(commandsDir, bundleDir, DEFAULTS_DIR2, id, COMMAND_EXTENSION)) {
4917
- await restoreDefault(commandsDir, bundleDir, DEFAULTS_DIR_ALT2, id, COMMAND_EXTENSION);
4918
- }
4919
- }
4920
- }
4921
- async function loadAllCommands(configDir) {
4922
- return loadItems(getCommandsDir(configDir), COMMAND_EXTENSION);
4923
- }
4924
- async function getDefaultCommandIds() {
4925
- const bundleDir = getBundleDir();
4926
- const ids = await getDefaultIds(bundleDir, DEFAULTS_DIR2, COMMAND_EXTENSION);
4927
- if (ids.length) return ids;
4928
- return getDefaultIds(bundleDir, DEFAULTS_DIR_ALT2, COMMAND_EXTENSION);
4929
- }
4930
- async function restoreDefaultCommand(configDir, commandId) {
4931
- const bundleDir = getBundleDir();
4932
- const commandsDir = getCommandsDir(configDir);
4933
- return restoreDefault(commandsDir, bundleDir, DEFAULTS_DIR2, commandId, COMMAND_EXTENSION) || restoreDefault(commandsDir, bundleDir, DEFAULTS_DIR_ALT2, commandId, COMMAND_EXTENSION);
4934
- }
4935
- async function getModifiedDefaultCommandIds(configDir) {
4936
- const bundleDir = getBundleDir();
4937
- const ids = await getDefaultCommandIds();
4938
- const files = (await readdir2(join4(bundleDir, DEFAULTS_DIR2))).filter((f) => f.endsWith(COMMAND_EXTENSION));
4939
- return files.map((f) => f.replace(COMMAND_EXTENSION, "")).filter((id) => ids.includes(id));
4940
- }
4941
- async function restoreAllDefaultCommands(configDir) {
4942
- const ids = await getDefaultCommandIds();
4943
- let count = 0;
4944
- for (const id of ids) {
4945
- if (await restoreDefaultCommand(configDir, id)) count++;
4946
- }
4947
- return count;
4948
- }
4949
- function findCommandById(commandId, commands) {
4950
- return findById(commandId, commands);
4951
- }
4952
- async function commandExists(configDir, commandId) {
4953
- return dirExists(join4(getCommandsDir(configDir), `${commandId}${COMMAND_EXTENSION}`));
4954
- }
4955
- async function saveCommand(configDir, command) {
4956
- return saveItem(getCommandsDir(configDir), command.metadata.id, COMMAND_EXTENSION, command);
4957
- }
4958
- async function deleteCommand(configDir, commandId) {
4959
- return deleteItem(getCommandsDir(configDir), commandId, COMMAND_EXTENSION);
4871
+ // src/server/routes/skills.ts
4872
+ import { Router } from "express";
4873
+
4874
+ // src/server/routes/crud-helpers.ts
4875
+ function computeOverrideIds(defaults, userItems) {
4876
+ return userItems.filter((u) => defaults.some((d) => d.metadata.id === u.metadata.id)).map((u) => u.metadata.id);
4960
4877
  }
4961
4878
 
4962
4879
  // src/server/routes/skills.ts
4963
- import { Router } from "express";
4964
4880
  function createSkillRoutes(configDir) {
4965
4881
  const router = Router();
4966
4882
  router.get("/", async (_req, res) => {
4967
- const [skills, defaultIds, modifiedIds] = await Promise.all([
4968
- loadAllSkills(configDir),
4969
- getDefaultSkillIds(),
4970
- getModifiedDefaultSkillIds(configDir)
4883
+ const [defaults, userItems] = await Promise.all([
4884
+ loadDefaultSkills(),
4885
+ loadUserSkills(configDir)
4971
4886
  ]);
4887
+ const overrideIds = computeOverrideIds(defaults, userItems);
4972
4888
  res.json({
4973
- skills: skills.map((s) => ({
4889
+ defaults: defaults.map((s) => ({
4974
4890
  ...s.metadata,
4975
4891
  enabled: isSkillEnabled(s.metadata.id)
4976
4892
  })),
4977
- defaultIds,
4978
- modifiedIds
4893
+ userItems: userItems.map((s) => ({
4894
+ ...s.metadata,
4895
+ enabled: isSkillEnabled(s.metadata.id)
4896
+ })),
4897
+ overrideIds
4979
4898
  });
4980
4899
  });
4900
+ router.get("/defaults/:id", async (req, res) => {
4901
+ const { id } = req.params;
4902
+ const content = await getDefaultSkillContent(id);
4903
+ if (!content) {
4904
+ return res.status(404).json({ error: "Default skill not found" });
4905
+ }
4906
+ res.json(content);
4907
+ });
4981
4908
  router.get("/default-ids", async (_req, res) => {
4982
4909
  const ids = await getDefaultSkillIds();
4983
4910
  res.json({ ids });
4984
4911
  });
4985
- router.post("/restore-all-defaults", async (_req, res) => {
4986
- const count = await restoreAllDefaultSkills(configDir);
4987
- res.json({ success: true, count });
4988
- });
4989
- router.post("/:id/restore-default", async (req, res) => {
4990
- const { id } = req.params;
4991
- const restored = await restoreDefaultSkill(configDir, id);
4992
- if (!restored) {
4993
- return res.status(404).json({ error: "No bundled default found for this skill" });
4994
- }
4995
- res.json({ success: true });
4996
- });
4997
4912
  router.post("/:id/toggle", (req, res) => {
4998
4913
  const { id } = req.params;
4999
4914
  const currentlyEnabled = isSkillEnabled(id);
@@ -5017,7 +4932,8 @@ function createSkillRoutes(configDir) {
5017
4932
  if (!/^[a-z0-9-]+$/.test(body.metadata.id)) {
5018
4933
  return res.status(400).json({ error: "Skill ID must be lowercase alphanumeric with hyphens only" });
5019
4934
  }
5020
- if (await skillExists(configDir, body.metadata.id)) {
4935
+ const exists = await skillExists(configDir, body.metadata.id);
4936
+ if (exists) {
5021
4937
  return res.status(409).json({ error: "A skill with this ID already exists" });
5022
4938
  }
5023
4939
  await saveSkill(configDir, body);
@@ -5025,15 +4941,12 @@ function createSkillRoutes(configDir) {
5025
4941
  });
5026
4942
  router.put("/:id", async (req, res) => {
5027
4943
  const { id } = req.params;
5028
- if (!await skillExists(configDir, id)) {
5029
- return res.status(404).json({ error: "Skill not found" });
5030
- }
5031
- const body = req.body;
5032
4944
  const skills = await loadAllSkills(configDir);
5033
4945
  const existing = findSkillById(id, skills);
5034
4946
  if (!existing) {
5035
4947
  return res.status(404).json({ error: "Skill not found" });
5036
4948
  }
4949
+ const body = req.body;
5037
4950
  const updated = {
5038
4951
  metadata: { ...existing.metadata, ...body.metadata, id },
5039
4952
  prompt: body.prompt ?? existing.prompt
@@ -5043,47 +4956,148 @@ function createSkillRoutes(configDir) {
5043
4956
  });
5044
4957
  router.delete("/:id", async (req, res) => {
5045
4958
  const { id } = req.params;
5046
- const deleted = await deleteSkill(configDir, id);
5047
- if (!deleted) {
5048
- return res.status(404).json({ error: "Skill not found" });
4959
+ const result = await deleteSkill(configDir, id);
4960
+ if (!result.success) {
4961
+ return res.status(403).json({ error: result.reason ?? "Cannot delete this skill" });
5049
4962
  }
5050
4963
  res.json({ success: true });
5051
4964
  });
4965
+ router.post("/:id/duplicate", async (req, res) => {
4966
+ const { id } = req.params;
4967
+ const defaults = await loadDefaultSkills();
4968
+ const userItems = await loadUserSkills(configDir);
4969
+ const source = defaults.find((s) => s.metadata.id === id) ?? userItems.find((s) => s.metadata.id === id);
4970
+ if (!source) {
4971
+ return res.status(404).json({ error: "Skill not found" });
4972
+ }
4973
+ const newId = `${id}-copy-${Date.now()}`;
4974
+ const duplicated = {
4975
+ metadata: { ...source.metadata, id: newId, name: `${source.metadata.name} (copy)` },
4976
+ prompt: source.prompt
4977
+ };
4978
+ await saveSkill(configDir, duplicated);
4979
+ res.status(201).json(duplicated);
4980
+ });
5052
4981
  return router;
5053
4982
  }
5054
4983
 
5055
4984
  // src/server/routes/commands.ts
5056
4985
  import { Router as Router2 } from "express";
4986
+
4987
+ // src/server/commands/registry.ts
4988
+ import { writeFile as writeFile3, mkdir as mkdir3, unlink as unlink2 } from "fs/promises";
4989
+ import { join as join4, dirname as dirname4 } from "path";
4990
+ import { fileURLToPath as fileURLToPath3 } from "url";
4991
+ import matter from "gray-matter";
4992
+ var __bundleDir2 = dirname4(fileURLToPath3(import.meta.url));
4993
+ var DEFAULTS_DIR2 = join4(__bundleDir2, "defaults");
4994
+ var DEFAULTS_DIR_ALT2 = join4(__bundleDir2, "command-defaults");
4995
+ var COMMAND_EXTENSION = ".command.md";
4996
+ function getCommandsDir(configDir) {
4997
+ return join4(configDir, "commands");
4998
+ }
4999
+ async function loadDefaultCommands() {
5000
+ let defaults = await loadItemsFromDir(DEFAULTS_DIR2, {
5001
+ extension: COMMAND_EXTENSION,
5002
+ logName: "command"
5003
+ });
5004
+ if (!defaults.length) {
5005
+ defaults = await loadItemsFromDir(DEFAULTS_DIR_ALT2, {
5006
+ extension: COMMAND_EXTENSION,
5007
+ logName: "command"
5008
+ });
5009
+ }
5010
+ return defaults;
5011
+ }
5012
+ async function loadUserCommands(configDir) {
5013
+ return loadItemsFromDir(getCommandsDir(configDir), {
5014
+ extension: COMMAND_EXTENSION,
5015
+ logName: "command"
5016
+ });
5017
+ }
5018
+ async function loadAllCommands(configDir) {
5019
+ const [defaultCommands, userCommands] = await Promise.all([
5020
+ loadDefaultCommands(),
5021
+ loadUserCommands(configDir)
5022
+ ]);
5023
+ const commandMap = /* @__PURE__ */ new Map();
5024
+ for (const cmd of defaultCommands) {
5025
+ commandMap.set(cmd.metadata.id, cmd);
5026
+ }
5027
+ for (const cmd of userCommands) {
5028
+ commandMap.set(cmd.metadata.id, cmd);
5029
+ }
5030
+ return Array.from(commandMap.values());
5031
+ }
5032
+ async function getDefaultCommandIds() {
5033
+ const ids = await getDefaultIds(DEFAULTS_DIR2, COMMAND_EXTENSION);
5034
+ if (ids.length) return ids;
5035
+ return getDefaultIds(DEFAULTS_DIR_ALT2, COMMAND_EXTENSION);
5036
+ }
5037
+ async function getDefaultCommandContent(commandId) {
5038
+ const defaults = await loadDefaultCommands();
5039
+ return defaults.find((c) => c.metadata.id === commandId) ?? null;
5040
+ }
5041
+ async function isDefaultCommand(commandId) {
5042
+ const defaultIds = await getDefaultCommandIds();
5043
+ return defaultIds.includes(commandId);
5044
+ }
5045
+ function findCommandById(commandId, commands) {
5046
+ return commands.find((c) => c.metadata.id === commandId);
5047
+ }
5048
+ async function commandExists(configDir, commandId) {
5049
+ return pathExists(join4(getCommandsDir(configDir), `${commandId}${COMMAND_EXTENSION}`));
5050
+ }
5051
+ async function saveCommand(configDir, command) {
5052
+ const commandsDir = getCommandsDir(configDir);
5053
+ if (!await pathExists(commandsDir)) {
5054
+ await mkdir3(commandsDir, { recursive: true });
5055
+ }
5056
+ const filePath = join4(commandsDir, `${command.metadata.id}${COMMAND_EXTENSION}`);
5057
+ const content = matter.stringify(command.prompt, command.metadata);
5058
+ await writeFile3(filePath, content, "utf-8");
5059
+ }
5060
+ async function deleteCommand(configDir, commandId) {
5061
+ const isDefault = await isDefaultCommand(commandId);
5062
+ if (isDefault) {
5063
+ return { success: false, reason: "Cannot delete built-in defaults" };
5064
+ }
5065
+ const filePath = join4(getCommandsDir(configDir), `${commandId}${COMMAND_EXTENSION}`);
5066
+ try {
5067
+ await unlink2(filePath);
5068
+ return { success: true };
5069
+ } catch {
5070
+ return { success: false };
5071
+ }
5072
+ }
5073
+
5074
+ // src/server/routes/commands.ts
5057
5075
  function createCommandRoutes(configDir) {
5058
5076
  const router = Router2();
5059
5077
  router.get("/", async (_req, res) => {
5060
- const [commands, defaultIds, modifiedIds] = await Promise.all([
5061
- loadAllCommands(configDir),
5062
- getDefaultCommandIds(),
5063
- getModifiedDefaultCommandIds(configDir)
5078
+ const [defaults, userItems] = await Promise.all([
5079
+ loadDefaultCommands(),
5080
+ loadUserCommands(configDir)
5064
5081
  ]);
5082
+ const overrideIds = computeOverrideIds(defaults, userItems);
5065
5083
  res.json({
5066
- commands: commands.map((c) => c.metadata),
5067
- defaultIds,
5068
- modifiedIds
5084
+ defaults: defaults.map((c) => c.metadata),
5085
+ userItems: userItems.map((c) => c.metadata),
5086
+ overrideIds
5069
5087
  });
5070
5088
  });
5089
+ router.get("/defaults/:id", async (req, res) => {
5090
+ const { id } = req.params;
5091
+ const content = await getDefaultCommandContent(id);
5092
+ if (!content) {
5093
+ return res.status(404).json({ error: "Default command not found" });
5094
+ }
5095
+ res.json(content);
5096
+ });
5071
5097
  router.get("/default-ids", async (_req, res) => {
5072
5098
  const ids = await getDefaultCommandIds();
5073
5099
  res.json({ ids });
5074
5100
  });
5075
- router.post("/restore-all-defaults", async (_req, res) => {
5076
- const count = await restoreAllDefaultCommands(configDir);
5077
- res.json({ success: true, count });
5078
- });
5079
- router.post("/:id/restore-default", async (req, res) => {
5080
- const { id } = req.params;
5081
- const restored = await restoreDefaultCommand(configDir, id);
5082
- if (!restored) {
5083
- return res.status(404).json({ error: "No bundled default found for this command" });
5084
- }
5085
- res.json({ success: true });
5086
- });
5087
5101
  router.get("/:id", async (req, res) => {
5088
5102
  const { id } = req.params;
5089
5103
  const commands = await loadAllCommands(configDir);
@@ -5101,7 +5115,8 @@ function createCommandRoutes(configDir) {
5101
5115
  if (!/^[a-z0-9-]+$/.test(body.metadata.id)) {
5102
5116
  return res.status(400).json({ error: "Command ID must be lowercase alphanumeric with hyphens only" });
5103
5117
  }
5104
- if (await commandExists(configDir, body.metadata.id)) {
5118
+ const exists = await commandExists(configDir, body.metadata.id);
5119
+ if (exists) {
5105
5120
  return res.status(409).json({ error: "A command with this ID already exists" });
5106
5121
  }
5107
5122
  await saveCommand(configDir, body);
@@ -5109,15 +5124,12 @@ function createCommandRoutes(configDir) {
5109
5124
  });
5110
5125
  router.put("/:id", async (req, res) => {
5111
5126
  const { id } = req.params;
5112
- if (!await commandExists(configDir, id)) {
5113
- return res.status(404).json({ error: "Command not found" });
5114
- }
5115
- const body = req.body;
5116
5127
  const commands = await loadAllCommands(configDir);
5117
5128
  const existing = findCommandById(id, commands);
5118
5129
  if (!existing) {
5119
5130
  return res.status(404).json({ error: "Command not found" });
5120
5131
  }
5132
+ const body = req.body;
5121
5133
  const updated = {
5122
5134
  metadata: { ...existing.metadata, ...body.metadata, id },
5123
5135
  prompt: body.prompt ?? existing.prompt
@@ -5127,12 +5139,28 @@ function createCommandRoutes(configDir) {
5127
5139
  });
5128
5140
  router.delete("/:id", async (req, res) => {
5129
5141
  const { id } = req.params;
5130
- const deleted = await deleteCommand(configDir, id);
5131
- if (!deleted) {
5132
- return res.status(404).json({ error: "Command not found" });
5142
+ const result = await deleteCommand(configDir, id);
5143
+ if (!result.success) {
5144
+ return res.status(403).json({ error: result.reason ?? "Cannot delete this command" });
5133
5145
  }
5134
5146
  res.json({ success: true });
5135
5147
  });
5148
+ router.post("/:id/duplicate", async (req, res) => {
5149
+ const { id } = req.params;
5150
+ const defaults = await loadDefaultCommands();
5151
+ const userItems = await loadUserCommands(configDir);
5152
+ const source = defaults.find((c) => c.metadata.id === id) ?? userItems.find((c) => c.metadata.id === id);
5153
+ if (!source) {
5154
+ return res.status(404).json({ error: "Command not found" });
5155
+ }
5156
+ const newId = `${id}-copy-${Date.now()}`;
5157
+ const duplicated = {
5158
+ metadata: { ...source.metadata, id: newId, name: `${source.metadata.name} (copy)` },
5159
+ prompt: source.prompt
5160
+ };
5161
+ await saveCommand(configDir, duplicated);
5162
+ res.status(201).json(duplicated);
5163
+ });
5136
5164
  return router;
5137
5165
  }
5138
5166
 
@@ -5141,33 +5169,29 @@ import { Router as Router3 } from "express";
5141
5169
  function createAgentRoutes(configDir) {
5142
5170
  const router = Router3();
5143
5171
  router.get("/", async (_req, res) => {
5144
- const [agents, defaultIds, modifiedIds] = await Promise.all([
5145
- loadAllAgents(configDir),
5146
- getDefaultAgentIds(),
5147
- getModifiedDefaultAgentIds(configDir)
5172
+ const [defaults, userItems] = await Promise.all([
5173
+ loadDefaultAgents(),
5174
+ loadUserAgents(configDir)
5148
5175
  ]);
5176
+ const overrideIds = computeOverrideIds(defaults, userItems);
5149
5177
  res.json({
5150
- agents: agents.map((a) => a.metadata),
5151
- defaultIds,
5152
- modifiedIds
5178
+ defaults: defaults.map((a) => a.metadata),
5179
+ userItems: userItems.map((a) => a.metadata),
5180
+ overrideIds
5153
5181
  });
5154
5182
  });
5183
+ router.get("/defaults/:id", async (req, res) => {
5184
+ const { id } = req.params;
5185
+ const content = await getDefaultAgentContent(id);
5186
+ if (!content) {
5187
+ return res.status(404).json({ error: "Default agent not found" });
5188
+ }
5189
+ res.json(content);
5190
+ });
5155
5191
  router.get("/default-ids", async (_req, res) => {
5156
5192
  const ids = await getDefaultAgentIds();
5157
5193
  res.json({ ids });
5158
5194
  });
5159
- router.post("/restore-all-defaults", async (_req, res) => {
5160
- const count = await restoreAllDefaultAgents(configDir);
5161
- res.json({ success: true, count });
5162
- });
5163
- router.post("/:id/restore-default", async (req, res) => {
5164
- const { id } = req.params;
5165
- const restored = await restoreDefaultAgent(configDir, id);
5166
- if (!restored) {
5167
- return res.status(404).json({ error: "No bundled default found for this agent" });
5168
- }
5169
- res.json({ success: true });
5170
- });
5171
5195
  router.get("/:id", async (req, res) => {
5172
5196
  const { id } = req.params;
5173
5197
  const agents = await loadAllAgents(configDir);
@@ -5182,7 +5206,8 @@ function createAgentRoutes(configDir) {
5182
5206
  if (!body?.metadata?.id || !body?.prompt) {
5183
5207
  return res.status(400).json({ error: "Missing required fields: metadata.id, prompt" });
5184
5208
  }
5185
- if (await agentExists(configDir, body.metadata.id)) {
5209
+ const exists = await agentExists(configDir, body.metadata.id);
5210
+ if (exists) {
5186
5211
  return res.status(409).json({ error: "An agent with this ID already exists" });
5187
5212
  }
5188
5213
  await saveAgent(configDir, body);
@@ -5208,12 +5233,28 @@ function createAgentRoutes(configDir) {
5208
5233
  });
5209
5234
  router.delete("/:id", async (req, res) => {
5210
5235
  const { id } = req.params;
5211
- const deleted = await deleteAgent(configDir, id);
5212
- if (!deleted) {
5213
- return res.status(404).json({ error: "Agent not found" });
5236
+ const result = await deleteAgent(configDir, id);
5237
+ if (!result.success) {
5238
+ return res.status(403).json({ error: result.reason ?? "Cannot delete this agent" });
5214
5239
  }
5215
5240
  res.json({ success: true });
5216
5241
  });
5242
+ router.post("/:id/duplicate", async (req, res) => {
5243
+ const { id } = req.params;
5244
+ const defaults = await loadDefaultAgents();
5245
+ const userItems = await loadUserAgents(configDir);
5246
+ const source = defaults.find((a) => a.metadata.id === id) ?? userItems.find((a) => a.metadata.id === id);
5247
+ if (!source) {
5248
+ return res.status(404).json({ error: "Agent not found" });
5249
+ }
5250
+ const newId = `${id}-copy-${Date.now()}`;
5251
+ const duplicated = {
5252
+ metadata: { ...source.metadata, id: newId, name: `${source.metadata.name} (copy)` },
5253
+ prompt: source.prompt
5254
+ };
5255
+ await saveAgent(configDir, duplicated);
5256
+ res.status(201).json(duplicated);
5257
+ });
5217
5258
  return router;
5218
5259
  }
5219
5260
 
@@ -5222,37 +5263,33 @@ import { Router as Router4 } from "express";
5222
5263
  function createWorkflowRoutes(configDir, config) {
5223
5264
  const router = Router4();
5224
5265
  router.get("/", async (_req, res) => {
5225
- const [workflows, defaultIds, modifiedIds] = await Promise.all([
5226
- loadAllWorkflows(configDir),
5227
- getDefaultWorkflowIds(),
5228
- getModifiedDefaultWorkflowIds(configDir)
5266
+ const [defaults, userItems] = await Promise.all([
5267
+ loadDefaultWorkflows(),
5268
+ loadUserWorkflows(configDir)
5229
5269
  ]);
5270
+ const overrideIds = computeOverrideIds(defaults, userItems);
5230
5271
  res.json({
5231
- workflows: workflows.map((p) => ({ ...p.metadata, startCondition: p.startCondition })),
5272
+ defaults: defaults.map((p) => ({ ...p.metadata, startCondition: p.startCondition })),
5273
+ userItems: userItems.map((p) => ({ ...p.metadata, startCondition: p.startCondition })),
5232
5274
  activeWorkflowId: config.activeWorkflowId ?? "default",
5233
- defaultIds,
5234
- modifiedIds
5275
+ overrideIds
5235
5276
  });
5236
5277
  });
5237
5278
  router.get("/template-variables", (_req, res) => {
5238
5279
  res.json({ variables: TEMPLATE_VARIABLES });
5239
5280
  });
5281
+ router.get("/defaults/:id", async (req, res) => {
5282
+ const { id } = req.params;
5283
+ const content = await getDefaultWorkflowContent(id);
5284
+ if (!content) {
5285
+ return res.status(404).json({ error: "Default workflow not found" });
5286
+ }
5287
+ res.json(content);
5288
+ });
5240
5289
  router.get("/default-ids", async (_req, res) => {
5241
5290
  const ids = await getDefaultWorkflowIds();
5242
5291
  res.json({ ids });
5243
5292
  });
5244
- router.post("/restore-all-defaults", async (_req, res) => {
5245
- const count = await restoreAllDefaultWorkflows(configDir);
5246
- res.json({ success: true, count });
5247
- });
5248
- router.post("/:id/restore-default", async (req, res) => {
5249
- const { id } = req.params;
5250
- const restored = await restoreDefaultWorkflow(configDir, id);
5251
- if (!restored) {
5252
- return res.status(404).json({ error: "No bundled default found for this workflow" });
5253
- }
5254
- res.json({ success: true });
5255
- });
5256
5293
  router.get("/:id", async (req, res) => {
5257
5294
  const { id } = req.params;
5258
5295
  const workflows = await loadAllWorkflows(configDir);
@@ -5267,7 +5304,8 @@ function createWorkflowRoutes(configDir, config) {
5267
5304
  if (!body?.metadata?.id || !body?.steps?.length) {
5268
5305
  return res.status(400).json({ error: "Missing required fields: metadata.id, steps" });
5269
5306
  }
5270
- if (await workflowExists(configDir, body.metadata.id)) {
5307
+ const exists = await workflowExists(configDir, body.metadata.id);
5308
+ if (exists) {
5271
5309
  return res.status(409).json({ error: "A workflow with this ID already exists" });
5272
5310
  }
5273
5311
  await saveWorkflow(configDir, body);
@@ -5275,7 +5313,9 @@ function createWorkflowRoutes(configDir, config) {
5275
5313
  });
5276
5314
  router.put("/:id", async (req, res) => {
5277
5315
  const { id } = req.params;
5278
- if (!await workflowExists(configDir, id)) {
5316
+ const workflows = await loadAllWorkflows(configDir);
5317
+ const existing = findWorkflowById(id, workflows);
5318
+ if (!existing) {
5279
5319
  return res.status(404).json({ error: "Workflow not found" });
5280
5320
  }
5281
5321
  const body = req.body;
@@ -5288,14 +5328,27 @@ function createWorkflowRoutes(configDir, config) {
5288
5328
  });
5289
5329
  router.delete("/:id", async (req, res) => {
5290
5330
  const { id } = req.params;
5291
- if (id === "default") {
5292
- return res.status(400).json({ error: "Cannot delete the default workflow" });
5331
+ const result = await deleteWorkflow(configDir, id);
5332
+ if (!result.success) {
5333
+ return res.status(403).json({ error: result.reason ?? "Cannot delete this workflow" });
5293
5334
  }
5294
- const deleted = await deleteWorkflow(configDir, id);
5295
- if (!deleted) {
5335
+ res.json({ success: true });
5336
+ });
5337
+ router.post("/:id/duplicate", async (req, res) => {
5338
+ const { id } = req.params;
5339
+ const defaults = await loadDefaultWorkflows();
5340
+ const userItems = await loadUserWorkflows(configDir);
5341
+ const source = defaults.find((w) => w.metadata.id === id) ?? userItems.find((w) => w.metadata.id === id);
5342
+ if (!source) {
5296
5343
  return res.status(404).json({ error: "Workflow not found" });
5297
5344
  }
5298
- res.json({ success: true });
5345
+ const newId = `${id}-copy-${Date.now()}`;
5346
+ const duplicated = {
5347
+ ...source,
5348
+ metadata: { ...source.metadata, id: newId, name: `${source.metadata.name} (copy)` }
5349
+ };
5350
+ await saveWorkflow(configDir, duplicated);
5351
+ res.status(201).json(duplicated);
5299
5352
  });
5300
5353
  return router;
5301
5354
  }
@@ -5419,10 +5472,10 @@ function createTerminalRoutes() {
5419
5472
  }
5420
5473
 
5421
5474
  // src/constants.ts
5422
- var VERSION = "1.6.15";
5475
+ var VERSION = "1.6.17";
5423
5476
 
5424
5477
  // src/server/index.ts
5425
- var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
5478
+ var __dirname2 = dirname5(fileURLToPath4(import.meta.url));
5426
5479
  async function createServerHandle(config) {
5427
5480
  setRuntimeConfig(config);
5428
5481
  setLogLevel(config.logging?.level ?? void 0, config.mode);
@@ -5430,10 +5483,6 @@ async function createServerHandle(config) {
5430
5483
  const db = initDatabase(config);
5431
5484
  initEventStore(db);
5432
5485
  const configDir = getGlobalConfigDir(config.mode ?? "production");
5433
- await ensureDefaultSkills(configDir);
5434
- await ensureDefaultCommands(configDir);
5435
- await ensureDefaultAgents(configDir);
5436
- await ensureDefaultWorkflows(configDir);
5437
5486
  const providerManager = createProviderManager(config);
5438
5487
  const sessionManager = new SessionManager(providerManager);
5439
5488
  const useMock = process.env["OPENFOX_MOCK_LLM"] === "true";
@@ -5725,7 +5774,7 @@ async function createServerHandle(config) {
5725
5774
  if (!callId || approved === void 0) {
5726
5775
  return res.status(400).json({ error: "callId and approved are required" });
5727
5776
  }
5728
- const { providePathConfirmation: providePathConfirmation2 } = await import("./tools-VOWNOP6V.js");
5777
+ const { providePathConfirmation: providePathConfirmation2 } = await import("./tools-YRDPOS6P.js");
5729
5778
  const result = providePathConfirmation2(callId, approved, alwaysAllow);
5730
5779
  if (!result.found) {
5731
5780
  return res.status(404).json({ error: "No pending path confirmation with that ID" });
@@ -5750,7 +5799,7 @@ async function createServerHandle(config) {
5750
5799
  if (!callId || !answer) {
5751
5800
  return res.status(400).json({ error: "callId and answer are required" });
5752
5801
  }
5753
- const { provideAnswer: provideAnswer2 } = await import("./tools-VOWNOP6V.js");
5802
+ const { provideAnswer: provideAnswer2 } = await import("./tools-YRDPOS6P.js");
5754
5803
  const found = provideAnswer2(callId, answer);
5755
5804
  if (!found) {
5756
5805
  return res.status(404).json({ error: "No pending question with that ID" });
@@ -5786,8 +5835,8 @@ async function createServerHandle(config) {
5786
5835
  if (!session) {
5787
5836
  return res.status(404).json({ error: "Session not found" });
5788
5837
  }
5789
- const { stopSessionExecution } = await import("./chat-handler-F2A2IMQB.js");
5790
- const { cancelQuestionsForSession: cancelQuestionsForSession2, cancelPathConfirmationsForSession: cancelPathConfirmationsForSession2 } = await import("./tools-VOWNOP6V.js");
5838
+ const { stopSessionExecution } = await import("./chat-handler-7HWNXQ4G.js");
5839
+ const { cancelQuestionsForSession: cancelQuestionsForSession2, cancelPathConfirmationsForSession: cancelPathConfirmationsForSession2 } = await import("./tools-YRDPOS6P.js");
5791
5840
  stopSessionExecution(sessionId, sessionManager);
5792
5841
  abortSession(sessionId);
5793
5842
  cancelQuestionsForSession2(sessionId, "Session stopped by user");
@@ -6109,7 +6158,7 @@ async function createServerHandle(config) {
6109
6158
  name: entry.name,
6110
6159
  path: join5(resolvedPath, entry.name)
6111
6160
  })).sort((a, b) => a.name.localeCompare(b.name));
6112
- const parent = dirname4(resolvedPath);
6161
+ const parent = dirname5(resolvedPath);
6113
6162
  const hasParent = parent !== resolvedPath;
6114
6163
  res.json({
6115
6164
  current: resolvedPath,
@@ -6235,7 +6284,7 @@ async function createServerHandle(config) {
6235
6284
  providerManager
6236
6285
  );
6237
6286
  const wss = wssExports.wss;
6238
- const { QueueProcessor } = await import("./processor-H5BQ5HQM.js");
6287
+ const { QueueProcessor } = await import("./processor-PVYCX5GK.js");
6239
6288
  const queueProcessor = new QueueProcessor({
6240
6289
  sessionManager,
6241
6290
  providerManager,
@@ -6308,4 +6357,4 @@ export {
6308
6357
  createServerHandle,
6309
6358
  createServer
6310
6359
  };
6311
- //# sourceMappingURL=chunk-G4E72ST3.js.map
6360
+ //# sourceMappingURL=chunk-UJANEDP5.js.map