@agent-spaces/server 0.1.2 → 0.2.3

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 (217) hide show
  1. package/dist/adapters/claude-code-runtime/index.js +4 -1
  2. package/dist/adapters/git.js +2 -21
  3. package/dist/agents/issue-agent-runner.js +35 -21
  4. package/dist/agents/issue-task-controller.js +120 -73
  5. package/dist/agents/planner-agent.js +9 -12
  6. package/dist/app.js +12 -1
  7. package/dist/hooks/agent-hooks.js +5 -8
  8. package/dist/package.json +1 -1
  9. package/dist/routes/agent.js +40 -17
  10. package/dist/routes/channel.js +4 -1
  11. package/dist/routes/command.js +108 -0
  12. package/dist/routes/folder.js +44 -7
  13. package/dist/routes/issue.js +57 -23
  14. package/dist/routes/mcp.js +50 -0
  15. package/dist/routes/skill.js +57 -0
  16. package/dist/routes/subscription.js +49 -0
  17. package/dist/routes/task.js +16 -1
  18. package/dist/routes/workflow.js +63 -0
  19. package/dist/routes/workspace.js +2 -21
  20. package/dist/services/agent.js +140 -76
  21. package/dist/services/builtin-tools.js +72 -0
  22. package/dist/services/channel.js +5 -5
  23. package/dist/services/command-process-manager.js +136 -0
  24. package/dist/services/command.js +48 -0
  25. package/dist/services/issue.js +5 -6
  26. package/dist/services/mcp.js +120 -0
  27. package/dist/services/notification-hub/bot-commands.js +48 -16
  28. package/dist/services/notification-hub/helpers.js +12 -0
  29. package/dist/services/pty.js +10 -3
  30. package/dist/services/skill.js +171 -0
  31. package/dist/services/subscription/aicode.js +45 -0
  32. package/dist/services/subscription/base.js +3 -0
  33. package/dist/services/subscription/index.js +20 -0
  34. package/dist/services/subscription/minimax.js +60 -0
  35. package/dist/services/subscription/zhipu.js +39 -0
  36. package/dist/services/workflow.js +203 -0
  37. package/dist/services/workspace.js +0 -1
  38. package/dist/storage/command-store.js +17 -0
  39. package/dist/storage/subscription-store.js +44 -0
  40. package/dist/storage/workflow-store.js +40 -0
  41. package/dist/web/404.html +1 -1
  42. package/dist/web/__next.__PAGE__.txt +4 -4
  43. package/dist/web/__next._full.txt +22 -21
  44. package/dist/web/__next._head.txt +4 -4
  45. package/dist/web/__next._index.txt +9 -8
  46. package/dist/web/__next._tree.txt +2 -2
  47. package/dist/web/_next/static/7UtFGbIZvN_2yiatRc2g3/_buildManifest.js +11 -0
  48. package/dist/web/_next/static/chunks/0-245kun-watp.js +2 -0
  49. package/dist/web/_next/static/chunks/0-ca_fo-yp3~z.js +1 -0
  50. package/dist/web/_next/static/chunks/0-vgd3j-zh3m8.js +2 -0
  51. package/dist/web/_next/static/chunks/0.4.g.8yf4rs0.js +3 -0
  52. package/dist/web/_next/static/chunks/02m_-ngl9w8co.js +1 -0
  53. package/dist/web/_next/static/chunks/03jbh7ud0jw~g.js +1 -0
  54. package/dist/web/_next/static/chunks/07kqxmubf5dua.js +1 -0
  55. package/dist/web/_next/static/chunks/088q-5_51dsrw.js +1 -0
  56. package/dist/web/_next/static/chunks/08fqgb~~a~4ck.js +1 -0
  57. package/dist/web/_next/static/chunks/09_ki3dc5cfwv.css +1 -0
  58. package/dist/web/_next/static/chunks/09jryrjps0vl2.js +1 -0
  59. package/dist/web/_next/static/chunks/0amzlgoqe50tv.js +8 -0
  60. package/dist/web/_next/static/chunks/0b2tump5duj9j.js +1 -0
  61. package/dist/web/_next/static/chunks/0car_w834cbb6.js +1 -0
  62. package/dist/web/_next/static/chunks/0fwvdy-ml8wpk.js +1 -0
  63. package/dist/web/_next/static/chunks/0g4vm6.v0o_lt.js +1 -0
  64. package/dist/web/_next/static/chunks/0g_b4t~000.o~.js +179 -0
  65. package/dist/web/_next/static/chunks/0gyede80jpy3n.js +4 -0
  66. package/dist/web/_next/static/chunks/0h256h-tu4e0r.js +2 -0
  67. package/dist/web/_next/static/chunks/0ib18ul605e~a.js +1 -0
  68. package/dist/web/_next/static/chunks/0j1g_rd9t5ot1.js +1 -0
  69. package/dist/web/_next/static/chunks/0jn~llaqcxo4q.js +1 -0
  70. package/dist/web/_next/static/chunks/0jvmviuftg5e2.css +1 -0
  71. package/dist/web/_next/static/chunks/0lb8l2~6s_owo.js +1 -0
  72. package/dist/web/_next/static/chunks/0o4m39hw4fb_j.js +1 -0
  73. package/dist/web/_next/static/chunks/0pep4mkvt3.rh.js +31 -0
  74. package/dist/web/_next/static/chunks/0pq2670_ezbcj.js +1 -0
  75. package/dist/web/_next/static/chunks/0q_scqkk9.t53.js +2 -0
  76. package/dist/web/_next/static/chunks/0qyjxx0y7rzuu.js +1 -0
  77. package/dist/web/_next/static/chunks/0rrdur.v1a5r7.js +1 -0
  78. package/dist/web/_next/static/chunks/0spo.tmfeas-o.js +1 -0
  79. package/dist/web/_next/static/chunks/0u88ij9dqqh~-.js +1 -0
  80. package/dist/web/_next/static/chunks/0zl19l5tuoppw.js +1 -0
  81. package/dist/web/_next/static/chunks/11n16hogah-5..js +1 -0
  82. package/dist/web/_next/static/chunks/1250wo~-5dgyx.js +1 -0
  83. package/dist/web/_next/static/chunks/14n8i2xz4_y-e.js +1 -0
  84. package/dist/web/_next/static/chunks/160ji-.dfvm20.js +1 -0
  85. package/dist/web/_next/static/chunks/turbopack-0lxiiw.jhevml.js +1 -0
  86. package/dist/web/_next/static/media/favicon.0~ekuj.zhggpa.ico +0 -0
  87. package/dist/web/_not-found/__next._full.txt +26 -19
  88. package/dist/web/_not-found/__next._head.txt +4 -4
  89. package/dist/web/_not-found/__next._index.txt +9 -8
  90. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  91. package/dist/web/_not-found/__next._not-found.txt +3 -3
  92. package/dist/web/_not-found/__next._tree.txt +2 -2
  93. package/dist/web/_not-found.html +1 -1
  94. package/dist/web/_not-found.txt +26 -19
  95. package/dist/web/apple-touch-icon.png +0 -0
  96. package/dist/web/favicon.ico +0 -0
  97. package/dist/web/icon-192.png +0 -0
  98. package/dist/web/icon-512.png +0 -0
  99. package/dist/web/index.html +1 -1
  100. package/dist/web/index.txt +22 -21
  101. package/dist/web/login/__next._full.txt +25 -23
  102. package/dist/web/login/__next._head.txt +4 -4
  103. package/dist/web/login/__next._index.txt +9 -8
  104. package/dist/web/login/__next._tree.txt +2 -2
  105. package/dist/web/login/__next.login.__PAGE__.txt +4 -4
  106. package/dist/web/login/__next.login.txt +3 -3
  107. package/dist/web/login.html +1 -1
  108. package/dist/web/login.txt +25 -23
  109. package/dist/web/settings/__next._full.txt +36 -0
  110. package/dist/web/settings/__next._head.txt +6 -0
  111. package/dist/web/settings/__next._index.txt +11 -0
  112. package/dist/web/settings/__next._tree.txt +8 -0
  113. package/dist/web/settings/__next.settings.__PAGE__.txt +9 -0
  114. package/dist/web/settings/__next.settings.txt +7 -0
  115. package/dist/web/settings/agents/__next._full.txt +39 -0
  116. package/dist/web/settings/agents/__next._head.txt +6 -0
  117. package/dist/web/settings/agents/__next._index.txt +11 -0
  118. package/dist/web/settings/agents/__next._tree.txt +8 -0
  119. package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +9 -0
  120. package/dist/web/settings/agents/__next.settings.agents.txt +5 -0
  121. package/dist/web/settings/agents/__next.settings.txt +7 -0
  122. package/dist/web/settings/agents.html +1 -0
  123. package/dist/web/settings/agents.txt +39 -0
  124. package/dist/web/settings/mcps/__next._full.txt +39 -0
  125. package/dist/web/settings/mcps/__next._head.txt +6 -0
  126. package/dist/web/settings/mcps/__next._index.txt +11 -0
  127. package/dist/web/settings/mcps/__next._tree.txt +8 -0
  128. package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +9 -0
  129. package/dist/web/settings/mcps/__next.settings.mcps.txt +5 -0
  130. package/dist/web/settings/mcps/__next.settings.txt +7 -0
  131. package/dist/web/settings/mcps.html +1 -0
  132. package/dist/web/settings/mcps.txt +39 -0
  133. package/dist/web/settings/models/__next._full.txt +39 -0
  134. package/dist/web/settings/models/__next._head.txt +6 -0
  135. package/dist/web/settings/models/__next._index.txt +11 -0
  136. package/dist/web/settings/models/__next._tree.txt +8 -0
  137. package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +9 -0
  138. package/dist/web/settings/models/__next.settings.models.txt +5 -0
  139. package/dist/web/settings/models/__next.settings.txt +7 -0
  140. package/dist/web/settings/models.html +1 -0
  141. package/dist/web/settings/models.txt +39 -0
  142. package/dist/web/settings/providers/__next._full.txt +39 -0
  143. package/dist/web/settings/providers/__next._head.txt +6 -0
  144. package/dist/web/settings/providers/__next._index.txt +11 -0
  145. package/dist/web/settings/providers/__next._tree.txt +8 -0
  146. package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +9 -0
  147. package/dist/web/settings/providers/__next.settings.providers.txt +5 -0
  148. package/dist/web/settings/providers/__next.settings.txt +7 -0
  149. package/dist/web/settings/providers.html +1 -0
  150. package/dist/web/settings/providers.txt +39 -0
  151. package/dist/web/settings/skills/__next._full.txt +39 -0
  152. package/dist/web/settings/skills/__next._head.txt +6 -0
  153. package/dist/web/settings/skills/__next._index.txt +11 -0
  154. package/dist/web/settings/skills/__next._tree.txt +8 -0
  155. package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +9 -0
  156. package/dist/web/settings/skills/__next.settings.skills.txt +5 -0
  157. package/dist/web/settings/skills/__next.settings.txt +7 -0
  158. package/dist/web/settings/skills.html +1 -0
  159. package/dist/web/settings/skills.txt +39 -0
  160. package/dist/web/settings.html +1 -0
  161. package/dist/web/settings.txt +36 -0
  162. package/dist/web/workflows/__next._full.txt +35 -0
  163. package/dist/web/workflows/__next._head.txt +6 -0
  164. package/dist/web/workflows/__next._index.txt +11 -0
  165. package/dist/web/workflows/__next._tree.txt +9 -0
  166. package/dist/web/workflows/__next.workflows.__PAGE__.txt +10 -0
  167. package/dist/web/workflows/__next.workflows.txt +5 -0
  168. package/dist/web/workflows.html +1 -0
  169. package/dist/web/workflows.txt +35 -0
  170. package/dist/web/workspace/_/__next._full.txt +25 -20
  171. package/dist/web/workspace/_/__next._head.txt +4 -4
  172. package/dist/web/workspace/_/__next._index.txt +9 -8
  173. package/dist/web/workspace/_/__next._tree.txt +2 -2
  174. package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
  175. package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
  176. package/dist/web/workspace/_/__next.workspace.txt +3 -3
  177. package/dist/web/workspace/_.html +1 -1
  178. package/dist/web/workspace/_.txt +25 -20
  179. package/dist/web/workspaces/__next._full.txt +25 -23
  180. package/dist/web/workspaces/__next._head.txt +4 -4
  181. package/dist/web/workspaces/__next._index.txt +9 -8
  182. package/dist/web/workspaces/__next._tree.txt +2 -2
  183. package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
  184. package/dist/web/workspaces/__next.workspaces.txt +3 -3
  185. package/dist/web/workspaces.html +1 -1
  186. package/dist/web/workspaces.txt +25 -23
  187. package/dist/ws/agent-prompt.js +84 -0
  188. package/dist/ws/agent-runner.js +592 -0
  189. package/dist/ws/handler.js +9 -1200
  190. package/dist/ws/html-utils.js +30 -0
  191. package/dist/ws/message-parts.js +498 -0
  192. package/package.json +12 -10
  193. package/dist/web/_next/static/chunks/038whpa0zpnas.js +0 -1
  194. package/dist/web/_next/static/chunks/06q5go~xoz5a3.js +0 -1
  195. package/dist/web/_next/static/chunks/095.wizobwtyg.js +0 -2
  196. package/dist/web/_next/static/chunks/0bfg.w~u-83h5.js +0 -1
  197. package/dist/web/_next/static/chunks/0ce7i6~sb20rv.js +0 -113
  198. package/dist/web/_next/static/chunks/0d4~pcva1uk-a.js +0 -1
  199. package/dist/web/_next/static/chunks/0iq70n_u75nyu.js +0 -1
  200. package/dist/web/_next/static/chunks/0memz-8zsbxpu.js +0 -1
  201. package/dist/web/_next/static/chunks/0nw~w.3~twebx.js +0 -2
  202. package/dist/web/_next/static/chunks/0pyytgz~5vt0f.js +0 -31
  203. package/dist/web/_next/static/chunks/0ujjcrz~1nq.q.css +0 -1
  204. package/dist/web/_next/static/chunks/0vdnx9n41dyjl.js +0 -1
  205. package/dist/web/_next/static/chunks/0yl4mqmxtll6g.js +0 -1
  206. package/dist/web/_next/static/chunks/0zcbfka5tcle3.js +0 -1
  207. package/dist/web/_next/static/chunks/0~eo58u99i.fr.js +0 -8
  208. package/dist/web/_next/static/chunks/13_2vxyccsv84.js +0 -4
  209. package/dist/web/_next/static/chunks/13wfs~urgxu0w.js +0 -1
  210. package/dist/web/_next/static/chunks/157~3c6wqkt83.js +0 -1
  211. package/dist/web/_next/static/chunks/15jvow_z4uq-..js +0 -1
  212. package/dist/web/_next/static/chunks/15v697nf~r-cy.js +0 -2
  213. package/dist/web/_next/static/chunks/turbopack-164~7ulq9o9yc.js +0 -1
  214. package/dist/web/_next/static/media/favicon.0x3dzn~oxb6tn.ico +0 -0
  215. package/dist/web/_next/static/owMnKmxATbtXK_J_k_uHh/_buildManifest.js +0 -21
  216. /package/dist/web/_next/static/{owMnKmxATbtXK_J_k_uHh → 7UtFGbIZvN_2yiatRc2g3}/_clientMiddlewareManifest.js +0 -0
  217. /package/dist/web/_next/static/{owMnKmxATbtXK_J_k_uHh → 7UtFGbIZvN_2yiatRc2g3}/_ssgManifest.js +0 -0
@@ -0,0 +1,63 @@
1
+ import { Router } from 'express';
2
+ import * as workflowService from '../services/workflow.js';
3
+ const router = Router();
4
+ router.get('/', (_req, res) => {
5
+ try {
6
+ const workflows = workflowService.listWorkflows();
7
+ res.json(workflows);
8
+ }
9
+ catch (error) {
10
+ res.status(500).json({ error: error.message });
11
+ }
12
+ });
13
+ router.get('/:workflowId', (req, res) => {
14
+ try {
15
+ const workflow = workflowService.getWorkflow(req.params.workflowId);
16
+ if (!workflow) {
17
+ res.status(404).json({ error: 'Workflow not found' });
18
+ return;
19
+ }
20
+ res.json(workflow);
21
+ }
22
+ catch (error) {
23
+ res.status(500).json({ error: error.message });
24
+ }
25
+ });
26
+ router.post('/', (req, res) => {
27
+ try {
28
+ const workflow = workflowService.createWorkflow(req.body);
29
+ res.status(201).json(workflow);
30
+ }
31
+ catch (error) {
32
+ res.status(400).json({ error: error.message });
33
+ }
34
+ });
35
+ router.put('/:workflowId', (req, res) => {
36
+ try {
37
+ const workflow = workflowService.updateWorkflow(req.params.workflowId, req.body);
38
+ res.json(workflow);
39
+ }
40
+ catch (error) {
41
+ res.status(400).json({ error: error.message });
42
+ }
43
+ });
44
+ router.delete('/:workflowId', (req, res) => {
45
+ try {
46
+ workflowService.deleteWorkflow(req.params.workflowId);
47
+ res.status(204).send();
48
+ }
49
+ catch (error) {
50
+ res.status(400).json({ error: error.message });
51
+ }
52
+ });
53
+ router.post('/:workflowId/duplicate', (req, res) => {
54
+ try {
55
+ const workflow = workflowService.duplicateWorkflow(req.params.workflowId);
56
+ res.status(201).json(workflow);
57
+ }
58
+ catch (error) {
59
+ res.status(400).json({ error: error.message });
60
+ }
61
+ });
62
+ export default router;
63
+ //# sourceMappingURL=workflow.js.map
@@ -73,27 +73,8 @@ router.put('/:id/prompt', (req, res) => {
73
73
  }
74
74
  res.json({ prompt: saved });
75
75
  });
76
- router.get('/:id/agent-templates', (req, res) => {
77
- const ws = wsService.getById(req.params.id);
78
- if (!ws) {
79
- res.status(404).json({ error: 'Workspace not found' });
80
- return;
81
- }
82
- const workspaceAgentIds = new Set((ws.agents || []).map((agent) => agent.id));
83
- res.json(agentService.listTemplates().filter((agent) => !workspaceAgentIds.has(agent.id)));
84
- });
85
- router.post('/:id/agents/from-templates', (req, res) => {
86
- const { agentIds } = req.body;
87
- if (!Array.isArray(agentIds) || agentIds.length === 0) {
88
- res.status(400).json({ error: 'agentIds are required' });
89
- return;
90
- }
91
- const added = agentService.addTemplatesToWorkspace(req.params.id, agentIds);
92
- if (!added) {
93
- res.status(404).json({ error: 'Workspace not found' });
94
- return;
95
- }
96
- res.status(201).json(added);
76
+ router.get('/:id/agent-templates', (_req, res) => {
77
+ res.json(agentService.listTemplates());
97
78
  });
98
79
  router.put('/:id', (req, res) => {
99
80
  const ws = wsService.update(req.params.id, req.body);
@@ -3,27 +3,23 @@ import { copyFileSync, cpSync, existsSync, readFileSync, readdirSync, rmSync, st
3
3
  import { basename, extname, isAbsolute, join, normalize, relative } from 'node:path';
4
4
  import { BUILT_IN_AGENT_TOOLS } from '@agent-spaces/shared';
5
5
  import { listAgentSessions, getAgentSession, createAgentSession, updateAgentSession, deleteAgentSession, getAgentUsageDashboard, recordAgentUsage, } from '../storage/agent-store.js';
6
- import { getWorkspace, updateWorkspace } from '../storage/workspace-store.js';
6
+ import { getWorkspace } from '../storage/workspace-store.js';
7
7
  import { listIssues, updateIssue } from '../storage/issue-store.js';
8
8
  import { listChannels, updateChannel } from './channel.js';
9
9
  import { ensureDir, getDataDir } from '../storage/json-store.js';
10
10
  import { extractUsageFromOutput } from '../storage/usage.js';
11
- const VALID_ROLES = ['scheduler', 'planner', 'executor', 'reviewer', 'commit', 'custom', 'bot'];
11
+ const DEFAULT_AGENT_ROLE = 'agent';
12
12
  const VALID_RUNTIME_KINDS = ['open-agent-sdk', 'claude-code', 'codex'];
13
13
  const VALID_TOOL_NAMES = new Set(BUILT_IN_AGENT_TOOLS.map((tool) => tool.name));
14
14
  const ANTHROPIC_BRIDGE_PROVIDERS = [
15
15
  'openai-responses-to-anthropic-messages',
16
16
  'openai-chat-completions-to-anthropic-messages',
17
17
  ];
18
- export function listPresets(workspaceId) {
19
- const ws = getWorkspace(workspaceId);
20
- if (!ws)
21
- return null;
22
- return ws.agents || [];
18
+ export function listPresets(_workspaceId) {
19
+ return listTemplates();
23
20
  }
24
- export function findEnabledPresetByRoleInMembers(workspaceId, memberIds, role) {
25
- const members = new Set(memberIds);
26
- return (listPresets(workspaceId) ?? []).find((agent) => members.has(agent.id) && agent.role === role && agent.enabled !== false) ?? null;
21
+ export function isValidRole(role) {
22
+ return typeof role === 'string' && role.trim().length > 0;
27
23
  }
28
24
  export function listTemplates() {
29
25
  const root = getGlobalAgentTemplatesDir();
@@ -34,39 +30,12 @@ export function listTemplates() {
34
30
  .map((entry) => readAgentTemplate(entry.name))
35
31
  .filter((template) => Boolean(template));
36
32
  }
37
- export function addTemplatesToWorkspace(workspaceId, templateIds) {
38
- const ws = getWorkspace(workspaceId);
39
- if (!ws)
40
- return null;
41
- const existingIds = new Set((ws.agents || []).map((agent) => agent.id));
42
- const added = [];
43
- for (const templateId of templateIds) {
44
- if (existingIds.has(templateId))
45
- continue;
46
- const template = readAgentTemplate(templateId);
47
- if (!template)
48
- continue;
49
- const workspaceAgentDir = getWorkspaceAgentDir(ws.agentspaceDir, templateId);
50
- const preset = {
51
- ...template,
52
- id: templateId,
53
- workingDir: workspaceAgentDir,
54
- };
55
- writeWorkspaceAgentCopy(preset, ws.agentspaceDir);
56
- ws.agents = [...(ws.agents || []), preset];
57
- existingIds.add(templateId);
58
- added.push(preset);
59
- }
60
- if (added.length > 0) {
61
- ws.updatedAt = new Date().toISOString();
62
- updateWorkspace(ws);
63
- }
64
- return added;
65
- }
66
33
  export async function testConnection(workspaceId, data) {
67
- const ws = getWorkspace(workspaceId);
68
- if (!ws)
69
- return null;
34
+ if (workspaceId) {
35
+ const ws = getWorkspace(workspaceId);
36
+ if (!ws)
37
+ return null;
38
+ }
70
39
  const apiBase = data.apiBase?.trim();
71
40
  const apiKey = data.apiKey?.trim();
72
41
  const model = data.modelId?.trim();
@@ -277,11 +246,7 @@ async function readErrorMessage(response) {
277
246
  return { message: body, body: body.slice(0, 2000) };
278
247
  }
279
248
  }
280
- export function createPreset(workspaceId, data) {
281
- const ws = getWorkspace(workspaceId);
282
- if (!ws)
283
- return null;
284
- const now = new Date().toISOString();
249
+ export function createPreset(_workspaceId, data) {
285
250
  const id = uuid();
286
251
  const workingDir = data.workingDir?.trim();
287
252
  const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
@@ -292,7 +257,7 @@ export function createPreset(workspaceId, data) {
292
257
  const preset = {
293
258
  id,
294
259
  name: data.name?.trim() || 'New Agent',
295
- role: data.role && VALID_ROLES.includes(data.role) ? data.role : 'executor',
260
+ role: isValidRole(data.role) ? data.role.trim() : DEFAULT_AGENT_ROLE,
296
261
  description: data.description || '',
297
262
  runtimeKind: presetRuntimeKind,
298
263
  modelProvider: requestedModelProvider,
@@ -308,25 +273,17 @@ export function createPreset(workspaceId, data) {
308
273
  maxTokens: data.maxTokens ?? 4096,
309
274
  sandboxDirs: data.sandboxDirs,
310
275
  maxRetries: data.maxRetries,
276
+ templateId: data.templateId,
311
277
  enabled: data.enabled ?? true,
312
278
  };
313
279
  writeAgentTemplate(preset, data.skills);
314
- if (!workingDir)
315
- writeWorkspaceAgentCopy(preset, ws.agentspaceDir);
316
- ws.agents = [...(ws.agents || []), preset];
317
- ws.updatedAt = now;
318
- updateWorkspace(ws);
319
280
  return preset;
320
281
  }
321
- export function updatePreset(workspaceId, presetId, data) {
322
- const ws = getWorkspace(workspaceId);
323
- if (!ws)
324
- return null;
325
- const index = (ws.agents || []).findIndex((preset) => preset.id === presetId);
326
- if (index === -1)
282
+ export function updatePreset(_workspaceId, presetId, data) {
283
+ const existing = readAgentTemplate(presetId);
284
+ if (!existing)
327
285
  return null;
328
- const existing = ws.agents[index];
329
- const role = data.role && VALID_ROLES.includes(data.role) ? data.role : existing.role;
286
+ const role = isValidRole(data.role) ? data.role.trim() : existing.role;
330
287
  const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
331
288
  ? data.runtimeKind
332
289
  : existing.runtimeKind || 'open-agent-sdk';
@@ -346,9 +303,6 @@ export function updatePreset(workspaceId, presetId, data) {
346
303
  enabled: data.enabled ?? existing.enabled ?? true,
347
304
  };
348
305
  writeAgentTemplate(updated, data.skills);
349
- ws.agents[index] = updated;
350
- ws.updatedAt = new Date().toISOString();
351
- updateWorkspace(ws);
352
306
  return updated;
353
307
  }
354
308
  export function getAllowedTools(mcps) {
@@ -464,7 +418,15 @@ function writeAgentTemplate(preset, skillInputs) {
464
418
  ensureDir(skillsDir);
465
419
  writeFileSync(join(dir, 'agent.json'), JSON.stringify(preset, null, 2), 'utf-8');
466
420
  writeFileSync(join(dir, 'mcp.json'), JSON.stringify(preset.mcps ?? {}, null, 2), 'utf-8');
467
- if (skillInputs?.some((skill) => typeof skill !== 'string')) {
421
+ const hasObjectSkills = skillInputs?.some((skill) => typeof skill !== 'string');
422
+ console.log('[writeAgentTemplate]', {
423
+ agentId: preset.id,
424
+ skillsCount: preset.skills?.length ?? 0,
425
+ skills: preset.skills,
426
+ hasObjectSkills,
427
+ skillInputTypes: skillInputs?.map((s) => typeof s),
428
+ });
429
+ if (hasObjectSkills) {
468
430
  rmSync(skillsDir, { recursive: true, force: true });
469
431
  ensureDir(skillsDir);
470
432
  for (const skill of skillInputs) {
@@ -474,6 +436,41 @@ function writeAgentTemplate(preset, skillInputs) {
474
436
  writeFileSync(join(skillsDir, filename), skill.content ?? '', 'utf-8');
475
437
  }
476
438
  }
439
+ else if (preset.skills?.length) {
440
+ const globalSkillsDir = join(getDataDir(), 'skills');
441
+ const keepFiles = new Set(preset.skills.map((s) => s.endsWith('.md') ? s : `${s}.md`));
442
+ // Remove skill files no longer in the list
443
+ if (existsSync(skillsDir)) {
444
+ for (const existing of readdirSync(skillsDir)) {
445
+ if (existing.endsWith('.md') && !keepFiles.has(existing)) {
446
+ rmSync(join(skillsDir, existing), { force: true });
447
+ console.log('[writeAgentTemplate] removed stale skill:', existing);
448
+ }
449
+ }
450
+ }
451
+ // Copy / ensure skill files
452
+ for (const filename of keepFiles) {
453
+ const target = join(skillsDir, filename);
454
+ const globalSource = join(globalSkillsDir, filename);
455
+ console.log('[writeAgentTemplate] skill:', filename, 'globalExists:', existsSync(globalSource), 'targetExists:', existsSync(target));
456
+ if (existsSync(globalSource)) {
457
+ copyFileSync(globalSource, target);
458
+ }
459
+ else if (!existsSync(target)) {
460
+ writeFileSync(target, '', 'utf-8');
461
+ }
462
+ }
463
+ }
464
+ else {
465
+ // No skills — clean skills dir
466
+ if (existsSync(skillsDir)) {
467
+ for (const existing of readdirSync(skillsDir)) {
468
+ if (existing.endsWith('.md')) {
469
+ rmSync(join(skillsDir, existing), { force: true });
470
+ }
471
+ }
472
+ }
473
+ }
477
474
  }
478
475
  function copyAgentTemplateToWorkspace(agentId, agentspaceDir) {
479
476
  const sourceDir = getGlobalAgentTemplateDir(agentId);
@@ -510,7 +507,7 @@ function ensureWorkspaceAgentCopy(preset, agentspaceDir) {
510
507
  return;
511
508
  writeWorkspaceAgentCopy(preset, agentspaceDir);
512
509
  }
513
- function readAgentTemplate(agentId) {
510
+ export function readAgentTemplate(agentId) {
514
511
  const filePath = join(getGlobalAgentTemplateDir(agentId), 'agent.json');
515
512
  if (!existsSync(filePath))
516
513
  return null;
@@ -530,32 +527,99 @@ export function deletePreset(workspaceId, presetId) {
530
527
  const ws = getWorkspace(workspaceId);
531
528
  if (!ws)
532
529
  return null;
533
- const before = (ws.agents || []).length;
534
- ws.agents = (ws.agents || []).filter((preset) => preset.id !== presetId);
535
- if (ws.agents.length === before)
530
+ const template = readAgentTemplate(presetId);
531
+ if (!template)
536
532
  return false;
537
- ws.updatedAt = new Date().toISOString();
538
- updateWorkspace(ws);
533
+ // Delete global template
534
+ const templateDir = getGlobalAgentTemplateDir(presetId);
535
+ if (existsSync(templateDir))
536
+ rmSync(templateDir, { recursive: true, force: true });
539
537
  // Remove agent from all channels' members
540
538
  for (const ch of listChannels(workspaceId)) {
541
539
  if (ch.members.includes(presetId)) {
542
540
  updateChannel(workspaceId, ch.id, { members: ch.members.filter((m) => m !== presetId) });
543
541
  }
544
542
  }
545
- // Remove agent from all issues' members and assignedAgents
543
+ // Remove agent from all issues' members
546
544
  for (const issue of listIssues(workspaceId)) {
547
545
  const membersChanged = issue.members.includes(presetId);
548
- const assignedChanged = issue.assignedAgents.includes(presetId);
549
- if (membersChanged || assignedChanged) {
546
+ if (membersChanged) {
550
547
  updateIssue({
551
548
  ...issue,
552
- members: membersChanged ? issue.members.filter((m) => m !== presetId) : issue.members,
553
- assignedAgents: assignedChanged ? issue.assignedAgents.filter((a) => a !== presetId) : issue.assignedAgents,
549
+ members: issue.members.filter((m) => m !== presetId),
554
550
  });
555
551
  }
556
552
  }
557
553
  return true;
558
554
  }
555
+ export function createGlobalPreset(data) {
556
+ const id = uuid();
557
+ const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
558
+ ? data.runtimeKind
559
+ : 'open-agent-sdk';
560
+ const requestedModelProvider = normalizeModelProvider(data.modelProvider);
561
+ const presetRuntimeKind = isAnthropicBridgeProvider(requestedModelProvider) ? 'claude-code' : runtimeKind;
562
+ const preset = {
563
+ id,
564
+ name: data.name?.trim() || 'New Agent',
565
+ role: isValidRole(data.role) ? data.role.trim() : DEFAULT_AGENT_ROLE,
566
+ description: data.description || '',
567
+ runtimeKind: presetRuntimeKind,
568
+ modelProvider: requestedModelProvider,
569
+ modelId: data.modelId || 'claude-sonnet-4-6',
570
+ apiBase: data.apiBase || '',
571
+ apiKey: data.apiKey || '',
572
+ workingDir: '',
573
+ mcps: normalizeMcpConfig(data.mcps),
574
+ skills: normalizeSkillNames(data.skills),
575
+ tools: normalizeToolNames(data.tools ?? BUILT_IN_AGENT_TOOLS.map((tool) => tool.name)),
576
+ systemPrompt: data.systemPrompt || '',
577
+ temperature: data.temperature ?? 0.3,
578
+ maxTokens: data.maxTokens ?? 4096,
579
+ sandboxDirs: data.sandboxDirs,
580
+ maxRetries: data.maxRetries,
581
+ templateId: data.templateId,
582
+ enabled: data.enabled ?? true,
583
+ };
584
+ writeAgentTemplate(preset, data.skills);
585
+ return preset;
586
+ }
587
+ export function updateGlobalPreset(presetId, data) {
588
+ const existing = readAgentTemplate(presetId);
589
+ if (!existing)
590
+ return null;
591
+ const role = isValidRole(data.role) ? data.role.trim() : existing.role;
592
+ const runtimeKind = data.runtimeKind && VALID_RUNTIME_KINDS.includes(data.runtimeKind)
593
+ ? data.runtimeKind
594
+ : existing.runtimeKind || 'open-agent-sdk';
595
+ const requestedModelProvider = normalizeModelProvider(data.modelProvider);
596
+ const updatedRuntimeKind = isAnthropicBridgeProvider(requestedModelProvider) ? 'claude-code' : runtimeKind;
597
+ const updated = {
598
+ ...existing,
599
+ ...data,
600
+ id: existing.id,
601
+ role,
602
+ runtimeKind: updatedRuntimeKind,
603
+ name: data.name?.trim() || existing.name || 'New Agent',
604
+ modelProvider: requestedModelProvider,
605
+ mcps: normalizeMcpConfig(data.mcps),
606
+ skills: normalizeSkillNames(data.skills),
607
+ tools: normalizeToolNames(data.tools ?? existing.tools),
608
+ enabled: data.enabled ?? existing.enabled ?? true,
609
+ };
610
+ writeAgentTemplate(updated, data.skills);
611
+ return updated;
612
+ }
613
+ export function deleteGlobalPreset(presetId) {
614
+ const templateDir = getGlobalAgentTemplateDir(presetId);
615
+ if (!existsSync(templateDir))
616
+ return false;
617
+ rmSync(templateDir, { recursive: true, force: true });
618
+ return true;
619
+ }
620
+ export function listGlobalPresets() {
621
+ return listTemplates();
622
+ }
559
623
  export function list(workspaceId) {
560
624
  return listAgentSessions(workspaceId);
561
625
  }
@@ -4,6 +4,8 @@ import * as issueCommentService from './issue-comment.js';
4
4
  import * as channelService from './channel.js';
5
5
  import * as taskService from './task.js';
6
6
  import * as agentService from './agent.js';
7
+ import * as commandService from './command.js';
8
+ import * as commandProcessManager from './command-process-manager.js';
7
9
  const currentChannelInputSchema = {
8
10
  type: 'object',
9
11
  properties: {
@@ -181,4 +183,74 @@ function assertCurrentChannelId(channel, input) {
181
183
  }
182
184
  return data;
183
185
  }
186
+ export function createCommandFunctionTools(workspaceId) {
187
+ return [
188
+ {
189
+ name: 'ListQuickCommands',
190
+ description: 'List all quick commands for the workspace with running status.',
191
+ inputSchema: {
192
+ type: 'object',
193
+ properties: {
194
+ workspaceId: { type: 'string', description: 'The workspace ID' },
195
+ },
196
+ required: ['workspaceId'],
197
+ additionalProperties: false,
198
+ },
199
+ annotations: { readOnly: true, openWorld: false },
200
+ execute: async (input) => {
201
+ const data = input;
202
+ if (data.workspaceId !== workspaceId)
203
+ throw new Error('workspaceId mismatch');
204
+ const commands = commandService.listCommands(workspaceId);
205
+ const processes = commandProcessManager.getCommandProcesses(workspaceId);
206
+ const processMap = new Map(processes.map(p => [p.commandId, p]));
207
+ return commands.map(cmd => ({
208
+ ...cmd,
209
+ running: processMap.has(cmd.id) ? processMap.get(cmd.id).status : false,
210
+ }));
211
+ },
212
+ },
213
+ {
214
+ name: 'RunQuickCommand',
215
+ description: 'Run a quick command by ID. Returns sessionId.',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {
219
+ workspaceId: { type: 'string' },
220
+ commandId: { type: 'string' },
221
+ },
222
+ required: ['workspaceId', 'commandId'],
223
+ additionalProperties: false,
224
+ },
225
+ annotations: { destructive: false, openWorld: false },
226
+ execute: async (input) => {
227
+ const data = input;
228
+ if (data.workspaceId !== workspaceId)
229
+ throw new Error('workspaceId mismatch');
230
+ return { sessionId: commandProcessManager.runCommand(workspaceId, data.commandId) };
231
+ },
232
+ },
233
+ {
234
+ name: 'StopQuickCommand',
235
+ description: 'Stop a running quick command by ID.',
236
+ inputSchema: {
237
+ type: 'object',
238
+ properties: {
239
+ workspaceId: { type: 'string' },
240
+ commandId: { type: 'string' },
241
+ },
242
+ required: ['workspaceId', 'commandId'],
243
+ additionalProperties: false,
244
+ },
245
+ annotations: { destructive: false, openWorld: false },
246
+ execute: async (input) => {
247
+ const data = input;
248
+ if (data.workspaceId !== workspaceId)
249
+ throw new Error('workspaceId mismatch');
250
+ commandProcessManager.stopCommand(workspaceId, data.commandId);
251
+ return { stopped: true };
252
+ },
253
+ },
254
+ ];
255
+ }
184
256
  //# sourceMappingURL=builtin-tools.js.map
@@ -2,7 +2,7 @@ import { v4 as uuid } from 'uuid';
2
2
  import { join } from 'node:path';
3
3
  import { readJsonFile, writeJsonFile, ensureDir, getDataDir } from '../storage/json-store.js';
4
4
  import { rmSync } from 'node:fs';
5
- import { getWorkspace } from '../storage/workspace-store.js';
5
+ import * as agentService from '../services/agent.js';
6
6
  function workspaceDir(workspaceId) {
7
7
  return join(getDataDir(), 'workspaces', workspaceId);
8
8
  }
@@ -56,19 +56,19 @@ export function updateChannel(workspaceId, channelId, data) {
56
56
  writeJsonFile(channelsPath(workspaceId), channels);
57
57
  return channels[idx];
58
58
  }
59
- function normalizeMembers(workspaceId, members = ['user']) {
60
- const agentIds = new Set((getWorkspace(workspaceId)?.agents || []).map((agent) => agent.id));
59
+ function normalizeMembers(workspaceId, members = []) {
60
+ const agentIds = new Set(agentService.listPresets(workspaceId).map((agent) => agent.id));
61
61
  const normalized = [];
62
62
  const seen = new Set();
63
63
  for (const member of members) {
64
- if (member !== 'user' && !agentIds.has(member))
64
+ if (!agentIds.has(member))
65
65
  continue;
66
66
  if (seen.has(member))
67
67
  continue;
68
68
  seen.add(member);
69
69
  normalized.push(member);
70
70
  }
71
- return normalized.includes('user') ? normalized : ['user', ...normalized];
71
+ return normalized;
72
72
  }
73
73
  export function deleteChannel(workspaceId, channelId) {
74
74
  const channels = listChannels(workspaceId);
@@ -0,0 +1,136 @@
1
+ import * as ptyService from './pty.js';
2
+ import * as commandService from './command.js';
3
+ import { getWorkspace } from '../storage/workspace-store.js';
4
+ import { broadcastToWorkspace } from '../ws/connection-manager.js';
5
+ const processes = new Map();
6
+ const sessionIndex = new Map();
7
+ const restartTimers = new Map();
8
+ export function runCommand(workspaceId, commandId) {
9
+ const existing = processes.get(commandId);
10
+ if (existing) {
11
+ console.log(`[command] reuse existing session=${existing.sessionId} for command=${commandId}`);
12
+ return existing.sessionId;
13
+ }
14
+ const command = commandService.getCommand(workspaceId, commandId);
15
+ if (!command)
16
+ throw new Error('Command not found');
17
+ const workspace = getWorkspace(workspaceId);
18
+ const cwd = command.cwd || workspace?.boundDirs[0] || process.env.HOME || '/tmp';
19
+ const shell = command.shell;
20
+ const env = command.env;
21
+ console.log(`[command] runCommand: workspace=${workspaceId} command=${commandId} cwd=${cwd} shell=${shell} cmd=${command.command}`);
22
+ let sessionId;
23
+ try {
24
+ sessionId = ptyService.createSession(workspaceId, cwd, (id, output) => {
25
+ broadcastToWorkspace(workspaceId, 'terminal.output', { sessionId: id, data: output });
26
+ }, (id, exitCode) => {
27
+ handlePtyExit(id, exitCode);
28
+ }, shell, env);
29
+ }
30
+ catch (err) {
31
+ console.error(`[command] pty.spawn failed: ${err.message}`);
32
+ throw new Error(`Failed to spawn terminal: ${err.message}`);
33
+ }
34
+ const now = new Date().toISOString();
35
+ const cmdProcess = {
36
+ commandId,
37
+ workspaceId,
38
+ sessionId,
39
+ status: 'running',
40
+ startedAt: now,
41
+ restartCount: 0,
42
+ };
43
+ processes.set(commandId, cmdProcess);
44
+ sessionIndex.set(sessionId, commandId);
45
+ ptyService.write(sessionId, command.command + '\r');
46
+ broadcastToWorkspace(workspaceId, 'terminal.created', { sessionId, cwd, shell });
47
+ console.log(`[command] broadcasted terminal.created: session=${sessionId} cwd=${cwd}`);
48
+ broadcastToWorkspace(workspaceId, 'command.started', {
49
+ commandId,
50
+ sessionId,
51
+ workspaceId,
52
+ });
53
+ return sessionId;
54
+ }
55
+ export function stopCommand(workspaceId, commandId) {
56
+ const cmdProcess = processes.get(commandId);
57
+ if (!cmdProcess)
58
+ throw new Error('Command not running');
59
+ console.log(`[command] stopCommand: command=${commandId} session=${cmdProcess.sessionId}`);
60
+ const timer = restartTimers.get(commandId);
61
+ if (timer) {
62
+ clearTimeout(timer);
63
+ restartTimers.delete(commandId);
64
+ }
65
+ ptyService.write(cmdProcess.sessionId, '\x03');
66
+ cmdProcess.status = 'stopping';
67
+ }
68
+ function handlePtyExit(sessionId, exitCode) {
69
+ const commandId = sessionIndex.get(sessionId);
70
+ if (!commandId)
71
+ return;
72
+ console.log(`[command] handlePtyExit: session=${sessionId} command=${commandId} exitCode=${exitCode}`);
73
+ const cmdProcess = processes.get(commandId);
74
+ if (!cmdProcess)
75
+ return;
76
+ processes.delete(commandId);
77
+ sessionIndex.delete(sessionId);
78
+ const timer = restartTimers.get(commandId);
79
+ if (timer) {
80
+ clearTimeout(timer);
81
+ restartTimers.delete(commandId);
82
+ }
83
+ const { workspaceId } = cmdProcess;
84
+ const command = commandService.getCommand(workspaceId, commandId);
85
+ if (command?.autoRestart === true && cmdProcess.status !== 'stopping') {
86
+ const restartCount = cmdProcess.restartCount + 1;
87
+ broadcastToWorkspace(workspaceId, 'command.restarted', {
88
+ commandId,
89
+ sessionId: cmdProcess.sessionId,
90
+ restartCount,
91
+ workspaceId,
92
+ });
93
+ const t = setTimeout(() => {
94
+ restartTimers.delete(commandId);
95
+ try {
96
+ runCommand(workspaceId, commandId);
97
+ }
98
+ catch {
99
+ // Command may have been deleted during delay
100
+ }
101
+ }, 1000);
102
+ restartTimers.set(commandId, t);
103
+ }
104
+ else {
105
+ broadcastToWorkspace(workspaceId, 'terminal.closed', { sessionId, exitCode });
106
+ broadcastToWorkspace(workspaceId, 'command.stopped', {
107
+ commandId,
108
+ exitCode,
109
+ workspaceId,
110
+ });
111
+ }
112
+ }
113
+ export function getCommandProcess(commandId) {
114
+ return processes.get(commandId);
115
+ }
116
+ export function getCommandProcesses(workspaceId) {
117
+ const result = [];
118
+ for (const cmdProcess of processes.values()) {
119
+ if (cmdProcess.workspaceId === workspaceId)
120
+ result.push(cmdProcess);
121
+ }
122
+ return result;
123
+ }
124
+ export function cleanup(workspaceId) {
125
+ for (const [commandId, cmdProcess] of processes) {
126
+ if (cmdProcess.workspaceId === workspaceId) {
127
+ const timer = restartTimers.get(commandId);
128
+ if (timer)
129
+ clearTimeout(timer);
130
+ restartTimers.delete(commandId);
131
+ sessionIndex.delete(cmdProcess.sessionId);
132
+ processes.delete(commandId);
133
+ }
134
+ }
135
+ }
136
+ //# sourceMappingURL=command-process-manager.js.map