@waishnav/devspace 1.0.3 → 1.0.4

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 (111) hide show
  1. package/dist/apply-patch.js +67 -64
  2. package/dist/cli.js +243 -4
  3. package/dist/config.js +6 -9
  4. package/dist/db/migrations.js +34 -0
  5. package/dist/db/schema.js +19 -0
  6. package/dist/local-agent-adapters.js +653 -0
  7. package/dist/local-agent-availability.js +119 -0
  8. package/dist/local-agent-path.js +24 -0
  9. package/dist/local-agent-profiles.js +135 -0
  10. package/dist/local-agent-runtime.js +48 -0
  11. package/dist/local-agent-store.js +145 -0
  12. package/dist/local-agent-targets.js +80 -0
  13. package/dist/process-sessions.js +11 -3
  14. package/dist/server.js +88 -50
  15. package/dist/skills.js +27 -3
  16. package/dist/ui/.vite/manifest.json +281 -281
  17. package/dist/ui/assets/{angular-html-D6nc7PYw.js → angular-html-CKeFzaPY.js} +1 -1
  18. package/dist/ui/assets/{angular-ts-BAIStLeI.js → angular-ts-0wcM_Zxf.js} +1 -1
  19. package/dist/ui/assets/{apl-CtEv5pop.js → apl-C7UdkNb2.js} +1 -1
  20. package/dist/ui/assets/{astro-CVFIMXc6.js → astro-DMqju4MJ.js} +1 -1
  21. package/dist/ui/assets/{blade-BgSlZ6jY.js → blade-DHanNgN5.js} +1 -1
  22. package/dist/ui/assets/{c-bxLlCV26.js → c-_iPKYPcx.js} +1 -1
  23. package/dist/ui/assets/{cobol-BuZ-r51r.js → cobol-CagdLJsE.js} +1 -1
  24. package/dist/ui/assets/{coffee-_JP0Joa7.js → coffee-CkNGriPi.js} +1 -1
  25. package/dist/ui/assets/{cpp-CP_eaz-H.js → cpp-CqmKOWIQ.js} +1 -1
  26. package/dist/ui/assets/{crystal-DZ-sEZd0.js → crystal-DbAbDEO1.js} +1 -1
  27. package/dist/ui/assets/{css-CZnyti9s.js → css-xwljL6Kw.js} +1 -1
  28. package/dist/ui/assets/{edge-DjsoDMTR.js → edge-CX3sYz15.js} +1 -1
  29. package/dist/ui/assets/{elixir-C5b04AkY.js → elixir-B3qL9hqd.js} +1 -1
  30. package/dist/ui/assets/{elm-Ddx-rgVY.js → elm-CH4vrjBp.js} +1 -1
  31. package/dist/ui/assets/{erb-cjF7oPcT.js → erb-CupHqynN.js} +1 -1
  32. package/dist/ui/assets/{git-rebase-DN5exBTl.js → git-rebase-CDxsU2LC.js} +1 -1
  33. package/dist/ui/assets/{glimmer-js-CEq0hNwa.js → glimmer-js-emkga3g1.js} +1 -1
  34. package/dist/ui/assets/{glimmer-ts-4uYGFkon.js → glimmer-ts-D8kQDnUw.js} +1 -1
  35. package/dist/ui/assets/{glsl-DEJ9tTWm.js → glsl-BtfTaPhi.js} +1 -1
  36. package/dist/ui/assets/{graphql-Bim5WuQh.js → graphql-DEus8iSO.js} +1 -1
  37. package/dist/ui/assets/{hack-BcyVIXhE.js → hack-DI8qpBqE.js} +1 -1
  38. package/dist/ui/assets/{haml-D9YaukDg.js → haml-Br1i1gmi.js} +1 -1
  39. package/dist/ui/assets/{handlebars-CFaMw9zH.js → handlebars-Ce23mu9x.js} +1 -1
  40. package/dist/ui/assets/{heavy-payload-B9aTqgfs.js → heavy-payload-BcZ_nGfF.js} +1 -1
  41. package/dist/ui/assets/{html-CgO_zx4D.js → html-RXVmfixf.js} +1 -1
  42. package/dist/ui/assets/{html-derivative-BxqkysJT.js → html-derivative-D1aqeobb.js} +1 -1
  43. package/dist/ui/assets/{http-CO5dv0RA.js → http-BebG00CE.js} +1 -1
  44. package/dist/ui/assets/{hurl-DRHtrK78.js → hurl-kHMuvenL.js} +1 -1
  45. package/dist/ui/assets/{java-BvHC_DgR.js → java-DYc2V5RJ.js} +1 -1
  46. package/dist/ui/assets/{javascript-C-3payQx.js → javascript-BcfzTzr3.js} +1 -1
  47. package/dist/ui/assets/{jinja-CAOPvEOH.js → jinja-D2RhihUZ.js} +1 -1
  48. package/dist/ui/assets/{jison-totCB1L-.js → jison-BuoFEePN.js} +1 -1
  49. package/dist/ui/assets/{json-D0J9rEe1.js → json-BIDkCuJA.js} +1 -1
  50. package/dist/ui/assets/{jsx-na-XQD_B.js → jsx-DHCL2HA0.js} +1 -1
  51. package/dist/ui/assets/{julia-CqrTtZ4-.js → julia-CvUQXscF.js} +1 -1
  52. package/dist/ui/assets/{just-pmrVSkMc.js → just-etc9R2qw.js} +1 -1
  53. package/dist/ui/assets/{latex-Cd_geX3E.js → latex-BB3zRnxC.js} +1 -1
  54. package/dist/ui/assets/{liquid-C6ky5sJ-.js → liquid-CIi5Mvpv.js} +1 -1
  55. package/dist/ui/assets/{lua-dgyBbeof.js → lua-Cox7XDiW.js} +1 -1
  56. package/dist/ui/assets/{marko-Dy5mfXY9.js → marko-ruBIRV0n.js} +1 -1
  57. package/dist/ui/assets/{mdc-7NUvxgI1.js → mdc-CcbvB9U5.js} +1 -1
  58. package/dist/ui/assets/{nginx-C-KiEXbI.js → nginx-DtWz6ZYE.js} +1 -1
  59. package/dist/ui/assets/{nim-BBw8kfl9.js → nim-BV5BR4P7.js} +1 -1
  60. package/dist/ui/assets/{perl-CyxhJgbO.js → perl-BvzoSaJ4.js} +1 -1
  61. package/dist/ui/assets/{php-m4LAOpam.js → php-C-clrAOy.js} +1 -1
  62. package/dist/ui/assets/{pug-Cpl0baYI.js → pug-E07_GXAr.js} +1 -1
  63. package/dist/ui/assets/{qml-BoSGNgIb.js → qml-dqt33dti.js} +1 -1
  64. package/dist/ui/assets/{r-C0JhqQd5.js → r-BcVQxsAw.js} +1 -1
  65. package/dist/ui/assets/{razor-CntsBNoh.js → razor-BW5T_UZ-.js} +1 -1
  66. package/dist/ui/assets/{regexp-BNZmOCRl.js → regexp-Ed_DEsuU.js} +1 -1
  67. package/dist/ui/assets/{review-payload-BgdWHWiv.js → review-payload-Dsmx33pY.js} +1 -1
  68. package/dist/ui/assets/{rst-BZ2XHIxM.js → rst-BkcGqLs8.js} +1 -1
  69. package/dist/ui/assets/{ruby-BIthfCTP.js → ruby-Sf_X_YID.js} +1 -1
  70. package/dist/ui/assets/{sas-5aT3fOla.js → sas-COsombsA.js} +1 -1
  71. package/dist/ui/assets/{scss-DZPfdkDy.js → scss-K6Ylfa26.js} +1 -1
  72. package/dist/ui/assets/{shellscript-DbhKc5Xj.js → shellscript-D2EdqkgV.js} +1 -1
  73. package/dist/ui/assets/{shellsession-X9-bcyHh.js → shellsession-jRXgXrnI.js} +1 -1
  74. package/dist/ui/assets/{soy-Bd4tecrW.js → soy-BGspKQWW.js} +1 -1
  75. package/dist/ui/assets/{sql-Chf5JrDn.js → sql-C4lAmYnM.js} +1 -1
  76. package/dist/ui/assets/{stata-DvMMIc_T.js → stata-DQWvRqxp.js} +1 -1
  77. package/dist/ui/assets/{surrealql-D5FMzVF5.js → surrealql-OmKt9XJ_.js} +1 -1
  78. package/dist/ui/assets/{svelte-CTiVXZrv.js → svelte-D0rUmwDI.js} +1 -1
  79. package/dist/ui/assets/{templ-ldnfq-7w.js → templ-B-U0RMc0.js} +1 -1
  80. package/dist/ui/assets/{tex-QW0_7T2r.js → tex-BIHD-btS.js} +1 -1
  81. package/dist/ui/assets/{ts-tags-CAKiWIBx.js → ts-tags-CGuDWfPC.js} +1 -1
  82. package/dist/ui/assets/{tsx-Bu9xJJmq.js → tsx-B0-h_HlD.js} +1 -1
  83. package/dist/ui/assets/{twig-Dp1752_V.js → twig-BN2qDJWN.js} +1 -1
  84. package/dist/ui/assets/{typescript-RfO03Kmm.js → typescript-BRnwPzY3.js} +1 -1
  85. package/dist/ui/assets/{useFileDiffInstance-DjqU4y1a.js → useFileDiffInstance-5ivdkUYO.js} +3 -3
  86. package/dist/ui/assets/{vue-Dlasv57G.js → vue-DekNU1qt.js} +1 -1
  87. package/dist/ui/assets/{vue-html-BHuJ7anu.js → vue-html-Bjlv-uvH.js} +1 -1
  88. package/dist/ui/assets/{vue-vine-CavE0PSk.js → vue-vine-C2Doytjq.js} +1 -1
  89. package/dist/ui/assets/{workspace-app-DGD9R89D.css → workspace-app-DASlX26z.css} +1 -1
  90. package/dist/ui/assets/{workspace-app-DDV_qdjx.js → workspace-app-DYi7VJam.js} +5 -5
  91. package/dist/ui/assets/{xml-Bbv64l4W.js → xml-pXbxxgI3.js} +1 -1
  92. package/dist/ui/assets/{xsl-Sro4gyQY.js → xsl-BEAlgDI2.js} +1 -1
  93. package/dist/ui/assets/{yaml-Du68c8sA.js → yaml-C1O-To71.js} +1 -1
  94. package/dist/ui/workspace-app.html +2 -2
  95. package/dist/user-config.js +21 -1
  96. package/dist/workspaces.js +3 -0
  97. package/docs/agent-profile-schema.md +165 -0
  98. package/docs/chatgpt-coding-workflow.md +24 -10
  99. package/docs/configuration.md +20 -8
  100. package/docs/gotchas.md +13 -0
  101. package/examples/agents/claude-implementer.md +30 -0
  102. package/examples/agents/codex-explorer.md +27 -0
  103. package/examples/agents/codex-qa-tester.md +29 -0
  104. package/examples/agents/codex-worker.md +25 -0
  105. package/examples/agents/copilot-reviewer.md +25 -0
  106. package/examples/agents/cursor-agent-worker.md +26 -0
  107. package/examples/agents/opencode-explorer.md +27 -0
  108. package/examples/agents/pi-reviewer.md +27 -0
  109. package/package.json +11 -3
  110. package/skills/subagent-delegation/SKILL.md +132 -0
  111. package/docs/codex-tool-mode-qa.md +0 -73
package/dist/server.js CHANGED
@@ -22,6 +22,8 @@ import { createReviewCheckpointManager } from "./review-checkpoints.js";
22
22
  import { formatPathForPrompt } from "./skills.js";
23
23
  import { createWorkspaceStore } from "./workspace-store.js";
24
24
  import { formatAgentsPath, WorkspaceRegistry } from "./workspaces.js";
25
+ import { summarizeLocalAgentProfile } from "./local-agent-profiles.js";
26
+ import { formatLocalAgentProviderAvailabilitySummary, getLocalAgentProviderAvailabilitySnapshot, } from "./local-agent-availability.js";
25
27
  const WORKSPACE_APP_URI = "ui://devspace/workspace-app.html";
26
28
  const WORKSPACE_APP_MANIFEST_ENTRY = "workspace-app.html";
27
29
  const WRITE_TOOL_ANNOTATIONS = {
@@ -64,32 +66,22 @@ function toolWidgetDescriptorMeta(config, kind) {
64
66
  },
65
67
  };
66
68
  }
67
- function toolNamesFor(config) {
68
- return config.toolNaming === "short" || config.toolMode === "codex"
69
- ? {
70
- openWorkspace: "open_workspace",
71
- read: "read",
72
- write: "write",
73
- edit: "edit",
74
- grep: "grep",
75
- glob: "glob",
76
- ls: "ls",
77
- shell: "bash",
78
- }
79
- : {
80
- openWorkspace: "open_workspace",
81
- read: "read_file",
82
- write: "write_file",
83
- edit: "edit_file",
84
- grep: "grep_files",
85
- glob: "find_files",
86
- ls: "list_directory",
87
- shell: "run_shell",
88
- };
89
- }
90
- function serverInstructions(config, toolNames) {
69
+ const toolNames = {
70
+ openWorkspace: "open_workspace",
71
+ read: "read",
72
+ write: "write",
73
+ edit: "edit",
74
+ grep: "grep",
75
+ glob: "glob",
76
+ ls: "ls",
77
+ shell: "bash",
78
+ };
79
+ function serverInstructions(config) {
80
+ const showChangesInstruction = config.widgets === "changes"
81
+ ? " If the turn successfully modifies files by creating, editing, overwriting, deleting, moving, or applying patches, call show_changes exactly once for that workspace after the final related file change and before your final response so the user can inspect the aggregate diff for that turn. Do not call it after every individual file change; do not skip it because individual file-change tools already returned diffs."
82
+ : "";
91
83
  if (config.toolMode === "codex") {
92
- return `Use DevSpace as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree and reuse its workspaceId. Use ${toolNames.read} for direct file reads, apply_patch for all file modifications, exec_command for inspection, tests, builds, and other commands, and write_stdin to poll or interact with running processes. Follow instructions returned by ${toolNames.openWorkspace}; read applicable instruction and skill files before working in their scope.`;
84
+ return `Use DevSpace as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree and reuse its workspaceId. Use ${toolNames.read} for direct file reads, apply_patch for all file modifications, exec_command for inspection, tests, builds, and other commands, and write_stdin to poll or interact with running processes. Follow instructions returned by ${toolNames.openWorkspace}; read applicable instruction and skill files before working in their scope.${showChangesInstruction}`;
93
85
  }
94
86
  const inspection = config.toolMode !== "full"
95
87
  ? `In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use ${toolNames.shell} with command-line tools such as grep, rg, find, ls, and tree for search and directory inspection. `
@@ -98,10 +90,18 @@ function serverInstructions(config, toolNames) {
98
90
  ? `When ${toolNames.openWorkspace} returns available skills and a task matches a skill, use ${toolNames.read} to read that skill's path before proceeding. Skill paths may be outside the workspace, but ${toolNames.read} only permits advertised SKILL.md files and files under already-loaded skill directories. `
99
91
  : "";
100
92
  const agentsMd = `Follow instructions returned by ${toolNames.openWorkspace}. Before working under a path listed in availableAgentsFiles, use ${toolNames.read} to inspect that instruction file and follow it. `;
101
- const showChanges = config.widgets === "changes"
102
- ? " After creating, editing, or overwriting files, call show_changes once after the related file changes are complete so the user can see the aggregate diff."
93
+ return `Use DevSpace as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree to obtain a workspaceId. Reuse that same workspaceId for all later file, search, edit, write, show-changes, and shell tools in that folder; do not call ${toolNames.openWorkspace} again unless switching folders/worktrees, changing checkout/worktree mode, the workspaceId is rejected as unknown, or the user explicitly asks to reopen. ${agentsMd}${skills}${inspection}Prefer ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not create or modify files with ${toolNames.shell}; avoid shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or any command whose purpose is to write project files.${showChangesInstruction}`;
94
+ }
95
+ function formatVisibleAgent(agent) {
96
+ const model = agent.model ? `, model ${agent.model}` : "";
97
+ const thinking = agent.thinking ? `, thinking ${agent.thinking}` : "";
98
+ const availability = agent.providerAvailable === false
99
+ ? `, unavailable: ${agent.providerUnavailableReason ?? "provider unavailable"}`
103
100
  : "";
104
- return `Use DevSpace as a local coding workspace. Call ${toolNames.openWorkspace} once per project folder or worktree to obtain a workspaceId. Reuse that same workspaceId for all later file, search, edit, write, show-changes, and shell tools in that folder; do not call ${toolNames.openWorkspace} again unless switching folders/worktrees, changing checkout/worktree mode, the workspaceId is rejected as unknown, or the user explicitly asks to reopen. ${agentsMd}${skills}${inspection}Prefer ${toolNames.edit} for targeted modifications, ${toolNames.write} only for new files or complete rewrites, and ${toolNames.shell} for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not create or modify files with ${toolNames.shell}; avoid shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or any command whose purpose is to write project files.${showChanges}`;
101
+ return `${agent.name} (${agent.provider}${model}${thinking}${availability})`;
102
+ }
103
+ function formatUnavailableAgentProvider(provider) {
104
+ return `${provider.name} (${provider.reason ?? "unavailable"})`;
105
105
  }
106
106
  function resultOutputSchema(extra = {}) {
107
107
  return {
@@ -120,6 +120,20 @@ const workspaceAgentsFileOutputSchema = z.object({
120
120
  path: z.string(),
121
121
  content: z.string(),
122
122
  });
123
+ const workspaceLocalAgentOutputSchema = z.object({
124
+ name: z.string(),
125
+ description: z.string(),
126
+ provider: z.string(),
127
+ model: z.string().optional(),
128
+ thinking: z.string().optional(),
129
+ providerAvailable: z.boolean().optional(),
130
+ providerUnavailableReason: z.string().optional(),
131
+ });
132
+ const workspaceLocalAgentProviderOutputSchema = z.object({
133
+ name: z.string(),
134
+ available: z.boolean(),
135
+ reason: z.string().optional(),
136
+ });
123
137
  const workspaceAvailableAgentsFileOutputSchema = z.object({
124
138
  path: z.string(),
125
139
  });
@@ -383,6 +397,7 @@ function registerCodexProcessTools(server, config, workspaces, processSessions)
383
397
  workspaceId,
384
398
  command: cmd,
385
399
  cwd,
400
+ workspaceRoot: workspace.root,
386
401
  tty,
387
402
  columns,
388
403
  rows,
@@ -460,15 +475,14 @@ function registerCodexProcessTools(server, config, workspaces, processSessions)
460
475
  });
461
476
  });
462
477
  }
463
- function createMcpServer(config, workspaces, reviewCheckpoints, processSessions) {
464
- const toolNames = toolNamesFor(config);
478
+ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders) {
465
479
  const server = new McpServer({
466
480
  name: "devspace",
467
481
  title: "DevSpace",
468
482
  version: "0.1.0",
469
483
  description: "Secure local coding workspace for MCP clients. Provides workspace-scoped file, search, edit, write, and shell tools.",
470
484
  }, {
471
- instructions: serverInstructions(config, toolNames),
485
+ instructions: serverInstructions(config),
472
486
  });
473
487
  registerAppResource(server, "DevSpace Diff Card", WORKSPACE_APP_URI, {
474
488
  description: "Interactive card for viewing DevSpace file diffs.",
@@ -528,6 +542,8 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
528
542
  agentsFiles: z.array(workspaceAgentsFileOutputSchema),
529
543
  availableAgentsFiles: z.array(workspaceAvailableAgentsFileOutputSchema),
530
544
  skills: z.array(workspaceSkillOutputSchema),
545
+ agentProviders: z.array(workspaceLocalAgentProviderOutputSchema),
546
+ agents: z.array(workspaceLocalAgentOutputSchema),
531
547
  skillDiagnostics: z.array(z.unknown()),
532
548
  instruction: z.string(),
533
549
  },
@@ -549,6 +565,16 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
549
565
  description: skill.description,
550
566
  path: formatPathForPrompt(skill.filePath),
551
567
  }));
568
+ const visibleAgentProviders = config.subagents ? localAgentProviders : [];
569
+ const visibleAgents = workspace.agentProfiles.map((profile) => {
570
+ const summary = summarizeLocalAgentProfile(profile);
571
+ const availability = visibleAgentProviders.find((provider) => provider.name === summary.provider);
572
+ return {
573
+ ...summary,
574
+ providerAvailable: availability?.available,
575
+ providerUnavailableReason: availability?.reason,
576
+ };
577
+ });
552
578
  const loadedAgentsFiles = agentsFiles.map((file) => ({
553
579
  path: formatAgentsPath(file.path, workspace.root),
554
580
  content: file.content,
@@ -575,6 +601,15 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
575
601
  visibleSkills.length > 0
576
602
  ? `Available skills: ${visibleSkills.map((skill) => skill.name).join(", ")}`
577
603
  : undefined,
604
+ visibleAgentProviders.some((provider) => provider.available)
605
+ ? `Available subagent providers: ${visibleAgentProviders.filter((provider) => provider.available).map((provider) => provider.name).join(", ")}`
606
+ : undefined,
607
+ visibleAgentProviders.some((provider) => !provider.available)
608
+ ? `Unavailable subagent providers: ${visibleAgentProviders.filter((provider) => !provider.available).map(formatUnavailableAgentProvider).join(", ")}`
609
+ : undefined,
610
+ visibleAgents.length > 0
611
+ ? `Available subagent profiles: ${visibleAgents.map(formatVisibleAgent).join(", ")}`
612
+ : undefined,
578
613
  instruction,
579
614
  ].filter(Boolean).join("\n"),
580
615
  },
@@ -598,6 +633,8 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
598
633
  agentsFiles: loadedAgentsFiles.length,
599
634
  availableAgentsFiles: availableAgentsFileOutputs.length,
600
635
  skills: visibleSkills.length,
636
+ agentProviders: visibleAgentProviders.length,
637
+ agents: visibleAgents.length,
601
638
  skillDiagnostics: workspace.skillDiagnostics.length,
602
639
  },
603
640
  },
@@ -611,6 +648,8 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
611
648
  agentsFiles: loadedAgentsFiles,
612
649
  availableAgentsFiles: availableAgentsFileOutputs,
613
650
  skills: visibleSkills,
651
+ agentProviders: visibleAgentProviders,
652
+ agents: visibleAgents,
614
653
  skillDiagnostics: workspace.skillDiagnostics,
615
654
  instruction,
616
655
  },
@@ -841,7 +880,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
841
880
  if (config.toolMode === "codex") {
842
881
  registerAppTool(server, "apply_patch", {
843
882
  title: "Apply patch",
844
- description: "Apply one Codex-style patch inside an open workspace. Supports adding, overwriting, updating, deleting, and moving files. Earlier successful file changes remain if a later patch action fails. Use this for all file modifications. Paths must be relative to the workspace. Call open_workspace first and pass workspaceId.",
883
+ description: "Apply one Codex-style patch inside an open workspace. Supports adding, overwriting, updating, deleting, and moving files. Use this for all file modifications. Paths must be relative to the workspace. Call open_workspace first and pass workspaceId.",
845
884
  inputSchema: {
846
885
  workspaceId: z
847
886
  .string()
@@ -904,31 +943,23 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
904
943
  if (config.widgets === "changes") {
905
944
  registerAppTool(server, "show_changes", {
906
945
  title: "Show changes",
907
- description: "Show aggregate file changes in an open workspace since the last shown checkpoint or since the workspace was opened. After you create, edit, or overwrite files, call this once when the related file changes are complete so the user can inspect the combined diff.",
946
+ description: "Show aggregate file changes for an open workspace. If the current turn successfully modified files, call this exactly once after the final related file change and before your final response so the user can inspect the combined diff for the turn. Do not call it after every individual file change, and do not skip it because prior file-change tools already displayed per-tool diffs.",
908
947
  inputSchema: {
909
948
  workspaceId: z
910
949
  .string()
911
950
  .describe("Workspace identifier returned by open_workspace."),
912
- since: z
913
- .enum(["last_shown", "workspace_open"])
914
- .optional()
915
- .describe("Defaults to last_shown. Use workspace_open to compare against the initial open_workspace checkpoint."),
916
- markReviewed: z
917
- .boolean()
918
- .optional()
919
- .describe("Defaults to true. When true, advances the last shown checkpoint to the current workspace state."),
920
951
  },
921
952
  outputSchema: resultOutputSchema(),
922
953
  ...toolWidgetDescriptorMeta(config, "show_changes"),
923
954
  annotations: { readOnlyHint: true },
924
- }, async ({ workspaceId, since, markReviewed }) => {
955
+ }, async ({ workspaceId }) => {
925
956
  const startedAt = performance.now();
926
957
  const workspace = workspaces.getWorkspace(workspaceId);
927
958
  const review = await reviewCheckpoints.reviewChanges({
928
959
  workspaceId,
929
960
  root: workspace.root,
930
- since: since ?? "last_shown",
931
- markReviewed: markReviewed ?? true,
961
+ since: "last_shown",
962
+ markReviewed: true,
932
963
  });
933
964
  const content = [textBlock(review.result)];
934
965
  logToolCall(config, {
@@ -958,7 +989,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
958
989
  }
959
990
  if (config.toolMode === "full") {
960
991
  registerAppTool(server, toolNames.grep, {
961
- title: config.toolNaming === "short" ? "Grep" : "Grep files",
992
+ title: "Grep",
962
993
  description: "Search file contents inside an open workspace. Use this before broad reads when looking for symbols, text, or usage sites. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
963
994
  inputSchema: {
964
995
  workspaceId: z
@@ -1020,7 +1051,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
1020
1051
  };
1021
1052
  });
1022
1053
  registerAppTool(server, toolNames.glob, {
1023
- title: config.toolNaming === "short" ? "Glob" : "Find files",
1054
+ title: "Glob",
1024
1055
  description: "Find files by glob pattern inside an open workspace. Use this to discover filenames or narrow file sets before reading. Respects project ignore rules. Call open_workspace first and pass workspaceId.",
1025
1056
  inputSchema: {
1026
1057
  workspaceId: z
@@ -1081,7 +1112,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
1081
1112
  };
1082
1113
  });
1083
1114
  registerAppTool(server, toolNames.ls, {
1084
- title: config.toolNaming === "short" ? "Ls" : "List directory",
1115
+ title: "Ls",
1085
1116
  description: "List a directory inside an open workspace. Use this for directory inspection before reading files. Call open_workspace first and pass workspaceId.",
1086
1117
  inputSchema: {
1087
1118
  workspaceId: z
@@ -1137,7 +1168,7 @@ function createMcpServer(config, workspaces, reviewCheckpoints, processSessions)
1137
1168
  }
1138
1169
  if (config.toolMode !== "codex") {
1139
1170
  registerAppTool(server, toolNames.shell, {
1140
- title: config.toolNaming === "short" ? "Bash" : "Run shell",
1171
+ title: "Bash",
1141
1172
  description: config.toolMode !== "full"
1142
1173
  ? `Run a shell command inside an open workspace. Use only for tests, builds, git inspection, package scripts, search, file discovery, and directory inspection. In minimal tool mode, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} are disabled; use command-line tools such as grep, rg, find, ls, and tree for those read-only inspection actions. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read} for direct file reads. Call open_workspace first and pass workspaceId. This is powerful local execution and should only be exposed behind strong authentication.`
1143
1174
  : `Run a shell command inside an open workspace. Use only for tests, builds, git inspection, package scripts, and commands that are better executed by the shell. Do not use ${toolNames.shell} to create or modify files. Do not use shell redirection, heredocs, tee, sed -i, perl -i, node/python/ruby scripts, or generated scripts to write project files; use ${toolNames.edit} for targeted changes and ${toolNames.write} for new files or full rewrites. Prefer ${toolNames.read}, ${toolNames.grep}, ${toolNames.glob}, and ${toolNames.ls} for file inspection. Call open_workspace first and pass workspaceId. This is powerful local execution and should only be exposed behind strong authentication.`,
@@ -1237,6 +1268,9 @@ export function createServer(config = loadConfig()) {
1237
1268
  const workspaces = new WorkspaceRegistry(config, workspaceStore);
1238
1269
  const reviewCheckpoints = createReviewCheckpointManager();
1239
1270
  const processSessions = new ProcessSessionManager();
1271
+ const localAgentProviders = config.subagents
1272
+ ? getLocalAgentProviderAvailabilitySnapshot()
1273
+ : [];
1240
1274
  if (config.logging.trustProxy) {
1241
1275
  app.set("trust proxy", true);
1242
1276
  }
@@ -1345,7 +1379,7 @@ export function createServer(config = loadConfig()) {
1345
1379
  });
1346
1380
  }
1347
1381
  };
1348
- const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions);
1382
+ const server = createMcpServer(config, workspaces, reviewCheckpoints, processSessions, localAgentProviders);
1349
1383
  await server.connect(transport);
1350
1384
  }
1351
1385
  else {
@@ -1368,6 +1402,7 @@ export function createServer(config = loadConfig()) {
1368
1402
  return {
1369
1403
  app,
1370
1404
  config,
1405
+ localAgentProviders,
1371
1406
  close: () => {
1372
1407
  if (closed)
1373
1408
  return;
@@ -1386,7 +1421,7 @@ async function isMainModule() {
1386
1421
  return modulePath === entrypointPath;
1387
1422
  }
1388
1423
  if (await isMainModule()) {
1389
- const { app, config, close } = createServer();
1424
+ const { app, config, close, localAgentProviders } = createServer();
1390
1425
  const httpServer = app.listen(config.port, config.host, () => {
1391
1426
  console.log(`devspace listening on http://${config.host}:${config.port}/mcp`);
1392
1427
  console.log(`allowed roots: ${config.allowedRoots.join(", ")}`);
@@ -1395,6 +1430,9 @@ if (await isMainModule()) {
1395
1430
  console.log(`request logging: ${config.logging.requests ? "enabled" : "disabled"}`);
1396
1431
  console.log(`asset logging: ${config.logging.assets ? "enabled" : "disabled"}`);
1397
1432
  console.log(`trust proxy: ${config.logging.trustProxy ? "enabled" : "disabled"}`);
1433
+ if (config.subagents) {
1434
+ console.log(`subagent providers: ${formatLocalAgentProviderAvailabilitySummary(localAgentProviders)}`);
1435
+ }
1398
1436
  });
1399
1437
  const shutdown = () => {
1400
1438
  httpServer.close(() => {
package/dist/skills.js CHANGED
@@ -1,14 +1,29 @@
1
1
  import { existsSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join, resolve, sep } from "node:path";
4
+ import { fileURLToPath } from "node:url";
4
5
  import { loadSkills, } from "@earendil-works/pi-coding-agent";
5
6
  import { expandHomePath, isPathInsideRoot } from "./roots.js";
7
+ const SUBAGENT_DELEGATION_NAME = "subagent-delegation";
8
+ const SUBAGENT_DELEGATION_SKILL = join(SUBAGENT_DELEGATION_NAME, "SKILL.md");
9
+ function bundledSkillsDir() {
10
+ return fileURLToPath(new URL("../skills", import.meta.url));
11
+ }
12
+ function hasSubagentDelegationSkill(skillDir) {
13
+ return existsSync(join(skillDir, SUBAGENT_DELEGATION_SKILL));
14
+ }
6
15
  export function effectiveSkillPaths(config, cwd) {
7
- const defaultPaths = [
16
+ const bundledSkills = bundledSkillsDir();
17
+ const defaultPathCandidates = [
8
18
  join(homedir(), ".agents", "skills"),
9
19
  resolve(cwd, ".agents", "skills"),
20
+ config.devspaceSkillsDir,
10
21
  join(config.agentDir, "skills"),
11
- ].filter((path) => existsSync(path));
22
+ config.subagents && !hasSubagentDelegationSkill(config.devspaceSkillsDir)
23
+ ? bundledSkills
24
+ : undefined,
25
+ ];
26
+ const defaultPaths = defaultPathCandidates.filter((path) => path !== undefined && existsSync(path));
12
27
  const seen = new Set();
13
28
  return [...defaultPaths, ...config.skillPaths]
14
29
  .map((path) => resolveSkillPath(path, cwd))
@@ -25,12 +40,21 @@ function resolveSkillPath(path, cwd) {
25
40
  export function loadWorkspaceSkills(config, cwd) {
26
41
  if (!config.skillsEnabled)
27
42
  return { skills: [], diagnostics: [] };
28
- return loadSkills({
43
+ const result = loadSkills({
29
44
  cwd,
30
45
  agentDir: config.agentDir,
31
46
  skillPaths: effectiveSkillPaths(config, cwd),
32
47
  includeDefaults: false,
33
48
  });
49
+ if (config.subagents)
50
+ return result;
51
+ return {
52
+ skills: result.skills.filter((skill) => skill.name !== SUBAGENT_DELEGATION_NAME),
53
+ diagnostics: result.diagnostics.filter((diagnostic) => {
54
+ const collision = diagnostic.collision;
55
+ return !(collision?.resourceType === "skill" && collision.name === SUBAGENT_DELEGATION_NAME);
56
+ }),
57
+ };
34
58
  }
35
59
  export function resolveSkillReadPath(skills, activatedSkillDirs, inputPath) {
36
60
  const absolutePath = resolve(expandHomePath(inputPath));