@agent-spaces/server 0.3.0 → 0.3.2

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 (187) hide show
  1. package/dist/adapters/claude-code-runtime/index.js +48 -6
  2. package/dist/adapters/claude-code-runtime/message-format.js +3 -3
  3. package/dist/adapters/git.js +140 -2
  4. package/dist/agents/agent-designer.js +334 -0
  5. package/dist/agents/agent-message-parts.js +8 -0
  6. package/dist/agents/commit-agent.js +9 -2
  7. package/dist/agents/issue-agent-runner.js +0 -1
  8. package/dist/agents/issue-task-controller.js +14 -4
  9. package/dist/app.js +51 -1
  10. package/dist/package.json +1 -1
  11. package/dist/routes/agent-sse.js +4 -1
  12. package/dist/routes/agent.js +15 -0
  13. package/dist/routes/channel.js +1 -1
  14. package/dist/routes/file.js +97 -1
  15. package/dist/routes/git.js +122 -1
  16. package/dist/routes/iframe-bookmarks.js +33 -0
  17. package/dist/routes/issue.js +12 -0
  18. package/dist/routes/speech-recognition.js +99 -0
  19. package/dist/services/agent.js +48 -5
  20. package/dist/services/builtin-tools.js +67 -1
  21. package/dist/services/channel.js +2 -0
  22. package/dist/services/command-process-manager.js +14 -1
  23. package/dist/services/file.js +86 -6
  24. package/dist/services/gitignore.js +65 -0
  25. package/dist/services/issue-retry.js +2 -1
  26. package/dist/services/issue.js +13 -4
  27. package/dist/services/notification-hub/bot-agent.js +9 -1
  28. package/dist/services/persistent-agent-context.js +155 -0
  29. package/dist/services/pty.js +47 -2
  30. package/dist/services/search.js +11 -3
  31. package/dist/services/speech-recognition/base.js +3 -0
  32. package/dist/services/speech-recognition/index.js +16 -0
  33. package/dist/services/speech-recognition/tencent.js +117 -0
  34. package/dist/storage/iframe-bookmarks-store.js +46 -0
  35. package/dist/storage/speech-recognition-store.js +61 -0
  36. package/dist/web/404.html +1 -1
  37. package/dist/web/__next.__PAGE__.txt +4 -4
  38. package/dist/web/__next._full.txt +31 -26
  39. package/dist/web/__next._head.txt +4 -4
  40. package/dist/web/__next._index.txt +17 -13
  41. package/dist/web/__next._tree.txt +2 -2
  42. package/dist/web/_next/static/chunks/00amm3dvu3po9.js +31 -0
  43. package/dist/web/_next/static/chunks/01hrib8z5bn-6.js +5 -0
  44. package/dist/web/_next/static/chunks/08nbjuc5i-ewu.js +3 -0
  45. package/dist/web/_next/static/chunks/0anuxr11yn5iv.js +2 -0
  46. package/dist/web/_next/static/chunks/0cxrev-hb8gkk.js +2 -0
  47. package/dist/web/_next/static/chunks/0egot8y~i-ge3.js +1 -0
  48. package/dist/web/_next/static/chunks/0e~0o6v_awcod.js +1 -0
  49. package/dist/web/_next/static/chunks/0f_dcx0nu5z7o.js +1 -0
  50. package/dist/web/_next/static/chunks/0fpf.rso9mhc0.js +1 -0
  51. package/dist/web/_next/static/chunks/0jc-4igj54z00.js +189 -0
  52. package/dist/web/_next/static/chunks/0plreh4_gkxf9.js +1 -0
  53. package/dist/web/_next/static/chunks/0u7_qd9hzqwac.js +1 -0
  54. package/dist/web/_next/static/chunks/0uo5x.bu.5u2-.css +1 -0
  55. package/dist/web/_next/static/chunks/0utxlfbckz4f5.js +1 -0
  56. package/dist/web/_next/static/chunks/0vesz93a98px4.js +1 -0
  57. package/dist/web/_next/static/chunks/0w1iudd1d46sw.js +8 -0
  58. package/dist/web/_next/static/chunks/0w3o.7vi.dlm6.js +1 -0
  59. package/dist/web/_next/static/chunks/0xtmr0y4vo-~p.js +1 -0
  60. package/dist/web/_next/static/chunks/11ozf700niy2z.js +1 -0
  61. package/dist/web/_next/static/chunks/{0jmsjlawtg6oa.js → 145gbwitjxamg.js} +1 -1
  62. package/dist/web/_next/static/chunks/{turbopack-0lxiiw.jhevml.js → turbopack-0kcwat8.leugk.js} +1 -1
  63. package/dist/web/_not-found/__next._full.txt +34 -30
  64. package/dist/web/_not-found/__next._head.txt +4 -4
  65. package/dist/web/_not-found/__next._index.txt +17 -13
  66. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  67. package/dist/web/_not-found/__next._not-found.txt +3 -3
  68. package/dist/web/_not-found/__next._tree.txt +2 -2
  69. package/dist/web/_not-found.html +1 -1
  70. package/dist/web/_not-found.txt +34 -30
  71. package/dist/web/index.html +1 -1
  72. package/dist/web/index.txt +31 -26
  73. package/dist/web/login/__next._full.txt +35 -29
  74. package/dist/web/login/__next._head.txt +4 -4
  75. package/dist/web/login/__next._index.txt +17 -13
  76. package/dist/web/login/__next._tree.txt +2 -2
  77. package/dist/web/login/__next.login.__PAGE__.txt +4 -4
  78. package/dist/web/login/__next.login.txt +3 -3
  79. package/dist/web/login.html +1 -1
  80. package/dist/web/login.txt +35 -29
  81. package/dist/web/settings/__next._full.txt +36 -34
  82. package/dist/web/settings/__next._head.txt +4 -4
  83. package/dist/web/settings/__next._index.txt +17 -13
  84. package/dist/web/settings/__next._tree.txt +2 -2
  85. package/dist/web/settings/__next.settings.__PAGE__.txt +4 -4
  86. package/dist/web/settings/__next.settings.txt +5 -5
  87. package/dist/web/settings/agents/__next._full.txt +38 -35
  88. package/dist/web/settings/agents/__next._head.txt +4 -4
  89. package/dist/web/settings/agents/__next._index.txt +17 -13
  90. package/dist/web/settings/agents/__next._tree.txt +2 -2
  91. package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +4 -4
  92. package/dist/web/settings/agents/__next.settings.agents.txt +3 -3
  93. package/dist/web/settings/agents/__next.settings.txt +5 -5
  94. package/dist/web/settings/agents.html +1 -1
  95. package/dist/web/settings/agents.txt +38 -35
  96. package/dist/web/settings/mcps/__next._full.txt +38 -35
  97. package/dist/web/settings/mcps/__next._head.txt +4 -4
  98. package/dist/web/settings/mcps/__next._index.txt +17 -13
  99. package/dist/web/settings/mcps/__next._tree.txt +2 -2
  100. package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +4 -4
  101. package/dist/web/settings/mcps/__next.settings.mcps.txt +3 -3
  102. package/dist/web/settings/mcps/__next.settings.txt +5 -5
  103. package/dist/web/settings/mcps.html +1 -1
  104. package/dist/web/settings/mcps.txt +38 -35
  105. package/dist/web/settings/models/__next._full.txt +38 -35
  106. package/dist/web/settings/models/__next._head.txt +4 -4
  107. package/dist/web/settings/models/__next._index.txt +17 -13
  108. package/dist/web/settings/models/__next._tree.txt +2 -2
  109. package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +4 -4
  110. package/dist/web/settings/models/__next.settings.models.txt +3 -3
  111. package/dist/web/settings/models/__next.settings.txt +5 -5
  112. package/dist/web/settings/models.html +1 -1
  113. package/dist/web/settings/models.txt +38 -35
  114. package/dist/web/settings/providers/__next._full.txt +38 -35
  115. package/dist/web/settings/providers/__next._head.txt +4 -4
  116. package/dist/web/settings/providers/__next._index.txt +17 -13
  117. package/dist/web/settings/providers/__next._tree.txt +2 -2
  118. package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +4 -4
  119. package/dist/web/settings/providers/__next.settings.providers.txt +3 -3
  120. package/dist/web/settings/providers/__next.settings.txt +5 -5
  121. package/dist/web/settings/providers.html +1 -1
  122. package/dist/web/settings/providers.txt +38 -35
  123. package/dist/web/settings/skills/__next._full.txt +38 -35
  124. package/dist/web/settings/skills/__next._head.txt +4 -4
  125. package/dist/web/settings/skills/__next._index.txt +17 -13
  126. package/dist/web/settings/skills/__next._tree.txt +2 -2
  127. package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +4 -4
  128. package/dist/web/settings/skills/__next.settings.skills.txt +3 -3
  129. package/dist/web/settings/skills/__next.settings.txt +5 -5
  130. package/dist/web/settings/skills.html +1 -1
  131. package/dist/web/settings/skills.txt +38 -35
  132. package/dist/web/settings.html +1 -1
  133. package/dist/web/settings.txt +36 -34
  134. package/dist/web/workflows/__next._full.txt +36 -31
  135. package/dist/web/workflows/__next._head.txt +4 -4
  136. package/dist/web/workflows/__next._index.txt +17 -13
  137. package/dist/web/workflows/__next._tree.txt +2 -2
  138. package/dist/web/workflows/__next.workflows.__PAGE__.txt +4 -4
  139. package/dist/web/workflows/__next.workflows.txt +3 -3
  140. package/dist/web/workflows.html +1 -1
  141. package/dist/web/workflows.txt +36 -31
  142. package/dist/web/workspace/_/__next._full.txt +39 -34
  143. package/dist/web/workspace/_/__next._head.txt +4 -4
  144. package/dist/web/workspace/_/__next._index.txt +17 -13
  145. package/dist/web/workspace/_/__next._tree.txt +2 -2
  146. package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
  147. package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
  148. package/dist/web/workspace/_/__next.workspace.txt +3 -3
  149. package/dist/web/workspace/_.html +1 -1
  150. package/dist/web/workspace/_.txt +39 -34
  151. package/dist/web/workspaces/__next._full.txt +35 -30
  152. package/dist/web/workspaces/__next._head.txt +4 -4
  153. package/dist/web/workspaces/__next._index.txt +17 -13
  154. package/dist/web/workspaces/__next._tree.txt +2 -2
  155. package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
  156. package/dist/web/workspaces/__next.workspaces.txt +3 -3
  157. package/dist/web/workspaces.html +1 -1
  158. package/dist/web/workspaces.txt +35 -30
  159. package/dist/ws/agent-prompt.js +48 -18
  160. package/dist/ws/agent-runner.js +10 -3
  161. package/dist/ws/message-parts.js +8 -0
  162. package/dist/ws/terminal-handler.js +1 -1
  163. package/package.json +1 -1
  164. package/public/avatars/1778953228323-5je54x-user.jpg +0 -0
  165. package/public/avatars/1778954057880-g01q6y-user.jpg +0 -0
  166. package/dist/web/_next/static/chunks/002-dxhags4dj.js +0 -2
  167. package/dist/web/_next/static/chunks/00fu7sjtftd3f.js +0 -1
  168. package/dist/web/_next/static/chunks/00s_1ft2-qz0r.js +0 -1
  169. package/dist/web/_next/static/chunks/02wkg1tjv~_t2.js +0 -1
  170. package/dist/web/_next/static/chunks/03jy~o.gx._e-.js +0 -3
  171. package/dist/web/_next/static/chunks/07bs5ds68tg38.js +0 -2
  172. package/dist/web/_next/static/chunks/0b1w9_ne4c2ez.js +0 -1
  173. package/dist/web/_next/static/chunks/0bzechrx76-ix.js +0 -1
  174. package/dist/web/_next/static/chunks/0csqa76ud3h4d.js +0 -1
  175. package/dist/web/_next/static/chunks/0fxdvo63ywaa~.js +0 -1
  176. package/dist/web/_next/static/chunks/0k5bmi-es-2nj.js +0 -1
  177. package/dist/web/_next/static/chunks/0pep4mkvt3.rh.js +0 -31
  178. package/dist/web/_next/static/chunks/0tqwdxb-8t_n1.js +0 -1
  179. package/dist/web/_next/static/chunks/0y5ipuqgqfrob.js +0 -8
  180. package/dist/web/_next/static/chunks/0yc41k980zabo.js +0 -1
  181. package/dist/web/_next/static/chunks/0zhhkzv4.0koq.js +0 -183
  182. package/dist/web/_next/static/chunks/0zl19l5tuoppw.js +0 -1
  183. package/dist/web/_next/static/chunks/102lz3yanx8i~.css +0 -1
  184. package/dist/web/_next/static/chunks/13wm9.vry~.lh.js +0 -5
  185. /package/dist/web/_next/static/{XzP71HbFE1fuE_bLT-x7i → 9X6R42UNn3h4WL_zj_Yh4}/_buildManifest.js +0 -0
  186. /package/dist/web/_next/static/{XzP71HbFE1fuE_bLT-x7i → 9X6R42UNn3h4WL_zj_Yh4}/_clientMiddlewareManifest.js +0 -0
  187. /package/dist/web/_next/static/{XzP71HbFE1fuE_bLT-x7i → 9X6R42UNn3h4WL_zj_Yh4}/_ssgManifest.js +0 -0
@@ -35,7 +35,6 @@ export async function runIssueAutomation(workspaceId, issueId, ctx) {
35
35
  return;
36
36
  }
37
37
  console.warn(`[issue-runner] no workflow configured for issue workspaceId=${workspaceId} issueId=${issueId}`);
38
- markIssueError(workspaceId, issueId, 'No workflow configured for issue', ctx);
39
38
  }
40
39
  export function hasActiveIssueAutomation(workspaceId) {
41
40
  return agentService.list(workspaceId).some((session) => session.status === 'active' &&
@@ -7,6 +7,7 @@ import * as workspaceService from '../services/workspace.js';
7
7
  import { mapWorkflowToTaskDrafts, validateWorkflowForRun } from '../services/workflow.js';
8
8
  import { createIssueFunctionTools } from '../services/builtin-tools.js';
9
9
  import { getThinkingRuntimeConfig } from '../services/llm-model-config.js';
10
+ import { prependPersistentAgentContext } from '../services/persistent-agent-context.js';
10
11
  import { completeIssueAgentProgress, createIssueAgentProgress, createIssueAgentProgressTracker } from './issue-agent-progress.js';
11
12
  const ACTIVE_TASK_STATUSES = ['running', 'reviewing', 'retrying', 'waiting_review'];
12
13
  export async function syncIssueTasksAfterPlanning(workspaceId, issueId, input, ctx) {
@@ -45,7 +46,7 @@ export async function syncIssueTasksAfterPlanning(workspaceId, issueId, input, c
45
46
  },
46
47
  });
47
48
  const runtime = createRuntimeForPreset(taskSyncPreset);
48
- const result = await runtime.execute(buildTaskSyncPrompt(issue, input, taskSyncWorkingDir), taskSyncWorkingDir, {
49
+ const result = await runtime.execute(buildIssueAgentPrompt(workspaceId, buildTaskSyncPrompt(issue, input, taskSyncWorkingDir), taskSyncWorkingDir, taskSyncPreset), taskSyncWorkingDir, {
49
50
  maxTurns: 20,
50
51
  mcpServers: undefined,
51
52
  functionTools: createTaskSyncTools(workspaceId, issue, taskSyncPreset, ctx),
@@ -184,7 +185,7 @@ export async function runIssueTask(workspaceId, issueId, taskId, ctx) {
184
185
  agentId: taskAgent.id,
185
186
  data: `[debug] workflow agentConfigId=${taskAgentPreset.id} role=${taskAgentPreset.role} taskAgentConfigId=${runningTask.agentConfigId || '(fallback)'} runtime=${taskAgentPreset.runtimeKind ?? 'open-agent-sdk'}`,
186
187
  });
187
- const result = await runtime.execute(buildTaskAgentPrompt(issue, runningTask, taskAgentPreset, agentWorkingDir), agentWorkingDir, {
188
+ const result = await runtime.execute(buildIssueAgentPrompt(workspaceId, buildTaskAgentPrompt(issue, runningTask, taskAgentPreset, agentWorkingDir), agentWorkingDir, taskAgentPreset), agentWorkingDir, {
188
189
  maxTurns: 100,
189
190
  mcpServers: agentService.getMcpServers(taskAgentPreset.mcps),
190
191
  functionTools: createCurrentIssueTools(workspaceId, issue, taskAgentPreset),
@@ -324,10 +325,10 @@ export function createTasksFromWorkflow(workspaceId, issueId, template, ctx) {
324
325
  }
325
326
  const drafts = mapWorkflowToTaskDrafts(template);
326
327
  issueService.updateStatus(workspaceId, issueId, 'planned');
327
- ctx.broadcast('issue.status_changed', { issueId, oldStatus: issue.status, newStatus: 'planned' });
328
+ ctx.broadcast('issue.status_changed', { issueId, from: issue.status, to: 'planned' });
328
329
  replaceIssueTasksFromDrafts(workspaceId, issueId, ctx, drafts);
329
330
  issueService.updateStatus(workspaceId, issueId, 'in_progress');
330
- ctx.broadcast('issue.status_changed', { issueId, oldStatus: 'planned', newStatus: 'in_progress' });
331
+ ctx.broadcast('issue.status_changed', { issueId, from: 'planned', to: 'in_progress' });
331
332
  ctx.broadcast('issue.updated', issueService.getById(workspaceId, issueId));
332
333
  scheduleRunnableIssueTasks(workspaceId, issueId, ctx).catch((err) => {
333
334
  console.error(`[workflow] task scheduling failed for issue ${issueId}:`, err);
@@ -435,6 +436,15 @@ function createRuntimeForPreset(preset) {
435
436
  function resolveIssueWorkspaceRoot(workspaceId) {
436
437
  return workspaceService.getById(workspaceId)?.boundDirs?.[0] || process.cwd();
437
438
  }
439
+ function buildIssueAgentPrompt(workspaceId, prompt, workingDir, preset) {
440
+ return prependPersistentAgentContext(prompt, {
441
+ workspaceId,
442
+ workingDir,
443
+ boundDirs: workspaceService.getById(workspaceId)?.boundDirs,
444
+ includeWorkspacePrompt: false,
445
+ excludeNativeClaudeMd: preset.runtimeKind === 'claude-code',
446
+ });
447
+ }
438
448
  function buildTaskSyncPrompt(issue, input, workingDir) {
439
449
  return [
440
450
  'You are the issue task synchronization controller.',
package/dist/app.js CHANGED
@@ -27,6 +27,7 @@ import subscriptionRouter from './routes/subscription.js';
27
27
  import agentSseRouter from './routes/agent-sse.js';
28
28
  import searchRouter from './routes/search.js';
29
29
  import notificationRouter from './routes/notification.js';
30
+ import speechRecognitionRouter, { handleSpeechStream } from './routes/speech-recognition.js';
30
31
  import { getUserSettings, setUserAvatarUrl, removeUserAvatarUrl } from './storage/user-settings-store.js';
31
32
  import { authMiddleware, verifyToken } from './middleware/auth.js';
32
33
  import { handleConnection } from './ws/handler.js';
@@ -100,6 +101,27 @@ app.use('/api/workspaces/:id/commands', commandRouter);
100
101
  app.use('/api/workspaces/:id/agents', agentRouter);
101
102
  app.use('/api/workspaces/:id/tasks', taskRouter);
102
103
  app.use('/api/workspaces/:id/git', gitRouter);
104
+ // Global git config (no workspace context needed)
105
+ import { gitGetConfig as _gitGetConfig, gitSetConfig as _gitSetConfig } from './adapters/git.js';
106
+ app.get('/api/git-config', async (_req, res) => {
107
+ try {
108
+ const config = await _gitGetConfig('global');
109
+ res.json(config);
110
+ }
111
+ catch (err) {
112
+ res.status(500).json({ error: err.message });
113
+ }
114
+ });
115
+ app.post('/api/git-config', async (req, res) => {
116
+ try {
117
+ const { name, email, proxy } = req.body;
118
+ await _gitSetConfig('global', { name, email, proxy });
119
+ res.json({ ok: true });
120
+ }
121
+ catch (err) {
122
+ res.status(500).json({ error: err.message });
123
+ }
124
+ });
103
125
  app.use('/api/workspaces/:id/search', searchRouter);
104
126
  app.use('/api/workspaces/:id/notifications', notificationRouter);
105
127
  app.use('/api/agents', agentRouter);
@@ -108,6 +130,7 @@ app.use('/api/folder', folderRouter);
108
130
  app.use('/api/skills', skillRouter);
109
131
  app.use('/api/mcps', mcpRouter);
110
132
  app.use('/api/subscriptions', subscriptionRouter);
133
+ app.use('/api/speech-recognition', speechRecognitionRouter);
111
134
  // Serve static web frontend in production (after API routes, before catch-all)
112
135
  const webDir = resolveRuntimeDir('web');
113
136
  if (existsSync(webDir)) {
@@ -134,7 +157,18 @@ else {
134
157
  console.warn('[server] web frontend not found at', webDir, '- run \`pnpm build\` first');
135
158
  }
136
159
  const server = createServer(app);
137
- const wss = new WebSocketServer({ server, path: '/ws' });
160
+ const wss = new WebSocketServer({ noServer: true });
161
+ // Speech recognition WebSocket on /ws/speech
162
+ const speechWss = new WebSocketServer({ noServer: true });
163
+ speechWss.on('connection', (ws, req) => {
164
+ const token = new URL(req.url || '', `http://localhost:${PORT}`).searchParams.get('token');
165
+ if (!verifyToken(token)) {
166
+ ws.close(4003, 'Unauthorized');
167
+ return;
168
+ }
169
+ const configId = new URL(req.url || '', `http://localhost:${PORT}`).searchParams.get('configId') || undefined;
170
+ handleSpeechStream(ws, configId);
171
+ });
138
172
  wss.on('connection', (ws, req) => {
139
173
  const url = new URL(req.url || '', `http://localhost:${PORT}`);
140
174
  const workspaceId = url.searchParams.get('workspaceId');
@@ -149,6 +183,22 @@ wss.on('connection', (ws, req) => {
149
183
  }
150
184
  handleConnection(ws, workspaceId);
151
185
  });
186
+ server.on('upgrade', (req, socket, head) => {
187
+ const { pathname } = new URL(req.url || '/', `http://localhost:${PORT}`);
188
+ if (pathname === '/ws') {
189
+ wss.handleUpgrade(req, socket, head, (ws) => {
190
+ wss.emit('connection', ws, req);
191
+ });
192
+ return;
193
+ }
194
+ if (pathname === '/ws/speech') {
195
+ speechWss.handleUpgrade(req, socket, head, (ws) => {
196
+ speechWss.emit('connection', ws, req);
197
+ });
198
+ return;
199
+ }
200
+ socket.destroy();
201
+ });
152
202
  const HOST = process.env.HOST || '0.0.0.0';
153
203
  server.listen(PORT, HOST, () => {
154
204
  console.log(`[server] listening on http://${HOST}:${PORT}`);
package/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-spaces/server",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "type": "module",
5
5
  "main": "app.js",
6
6
  "packageManager": "pnpm@10.17.1",
@@ -45,6 +45,7 @@ router.post('/run', async (req, res) => {
45
45
  const configDir = agentService.getAgentConfigDir(workspaceId, { ...preset, skills: requestedSkills });
46
46
  const skills = agentService.getAvailableSkillNames(configDir, requestedSkills);
47
47
  const output = [];
48
+ const workingDir = agentService.resolveWorkingDir(workspaceId, preset);
48
49
  let completed = false;
49
50
  prepareSse(res);
50
51
  writeSse(res, 'session', { session, workspaceId });
@@ -68,8 +69,10 @@ router.post('/run', async (req, res) => {
68
69
  mcpServers: Object.keys(mcpServers ?? {}),
69
70
  skills,
70
71
  boundDirs: workspace.boundDirs,
72
+ workingDir,
73
+ excludeNativeClaudeMd: preset.runtimeKind === 'claude-code',
71
74
  builtInTools: [],
72
- }), agentService.resolveWorkingDir(workspaceId, preset), {
75
+ }), workingDir, {
73
76
  maxTurns: normalizeMaxTurns(body.maxTurns),
74
77
  mcpServers,
75
78
  skills,
@@ -1,5 +1,6 @@
1
1
  import { Router } from 'express';
2
2
  import * as agentService from '../services/agent.js';
3
+ import { generateAgentDesign } from '../agents/agent-designer.js';
3
4
  const router = Router({ mergeParams: true });
4
5
  router.get('/usage/dashboard', (req, res) => {
5
6
  const days = Number(req.query.days ?? 30);
@@ -21,6 +22,20 @@ router.post('/presets', (req, res) => {
21
22
  const preset = agentService.createPreset(req.params.id, body);
22
23
  res.status(201).json(preset);
23
24
  });
25
+ router.post('/presets/generate', async (req, res) => {
26
+ const { prompt } = req.body;
27
+ if (!prompt?.trim()) {
28
+ res.status(400).json({ error: 'prompt is required' });
29
+ return;
30
+ }
31
+ try {
32
+ res.json(await generateAgentDesign(prompt));
33
+ }
34
+ catch (err) {
35
+ console.error('[agent-designer] generate failed', err);
36
+ res.status(400).json({ error: err instanceof Error ? err.message : 'agent generation failed' });
37
+ }
38
+ });
24
39
  router.post('/presets/test-connection', async (req, res) => {
25
40
  const workspaceId = req.params.id;
26
41
  const data = req.body;
@@ -24,7 +24,7 @@ router.post('/', (req, res) => {
24
24
  router.put('/:channelId', (req, res) => {
25
25
  const { id, channelId } = req.params;
26
26
  const patch = {};
27
- for (const key of ['name', 'type', 'issueId', 'members', 'pinnedMentionId', 'draft', 'todos']) {
27
+ for (const key of ['name', 'type', 'issueId', 'members', 'pinnedMentionId', 'draft', 'todos', 'archived']) {
28
28
  if (Object.hasOwn(req.body, key))
29
29
  patch[key] = req.body[key];
30
30
  }
@@ -12,7 +12,8 @@ router.get('/tree', async (req, res) => {
12
12
  return;
13
13
  }
14
14
  const path = req.query.path || '';
15
- const tree = await fileService.readTree(ws, path);
15
+ const depth = parseInt(req.query.depth) || undefined;
16
+ const tree = await fileService.readTree(ws, path, depth);
16
17
  res.json(tree);
17
18
  });
18
19
  router.get('/content', async (req, res) => {
@@ -144,5 +145,100 @@ router.post('/reveal', async (req, res) => {
144
145
  res.json({ ok: true, path: fullPath });
145
146
  });
146
147
  });
148
+ router.post('/rename', async (req, res) => {
149
+ const ws = fileService.getWorkspace(req.params.id);
150
+ if (!ws) {
151
+ res.status(404).json({ error: 'Workspace not found' });
152
+ return;
153
+ }
154
+ const { oldPath, newPath } = req.body;
155
+ if (!oldPath || !newPath) {
156
+ res.status(400).json({ error: 'oldPath and newPath are required' });
157
+ return;
158
+ }
159
+ const ok = await fileService.renamePath(ws, oldPath, newPath);
160
+ if (!ok) {
161
+ res.status(500).json({ error: 'Failed to rename' });
162
+ return;
163
+ }
164
+ res.json({ ok: true });
165
+ });
166
+ router.post('/copy', async (req, res) => {
167
+ const ws = fileService.getWorkspace(req.params.id);
168
+ if (!ws) {
169
+ res.status(404).json({ error: 'Workspace not found' });
170
+ return;
171
+ }
172
+ const { srcPath, destPath } = req.body;
173
+ if (!srcPath || !destPath) {
174
+ res.status(400).json({ error: 'srcPath and destPath are required' });
175
+ return;
176
+ }
177
+ const ok = await fileService.copyPath(ws, srcPath, destPath);
178
+ if (!ok) {
179
+ res.status(500).json({ error: 'Failed to copy' });
180
+ return;
181
+ }
182
+ res.json({ ok: true });
183
+ });
184
+ router.post('/import-url', async (req, res) => {
185
+ const ws = fileService.getWorkspace(req.params.id);
186
+ if (!ws) {
187
+ res.status(404).json({ error: 'Workspace not found' });
188
+ return;
189
+ }
190
+ const { url, targetDir } = req.body;
191
+ if (!url) {
192
+ res.status(400).json({ error: 'url is required' });
193
+ return;
194
+ }
195
+ const result = await fileService.importFromUrl(ws, url, targetDir || '');
196
+ if (!result) {
197
+ res.status(500).json({ error: 'Failed to import from URL' });
198
+ return;
199
+ }
200
+ res.json({ ok: true, path: result });
201
+ });
202
+ router.post('/import-path', async (req, res) => {
203
+ const ws = fileService.getWorkspace(req.params.id);
204
+ if (!ws) {
205
+ res.status(404).json({ error: 'Workspace not found' });
206
+ return;
207
+ }
208
+ const { absPath, targetDir } = req.body;
209
+ if (!absPath) {
210
+ res.status(400).json({ error: 'absPath is required' });
211
+ return;
212
+ }
213
+ const result = await fileService.importFromAbsPath(ws, absPath, targetDir || '');
214
+ if (!result) {
215
+ res.status(500).json({ error: 'Failed to import file' });
216
+ return;
217
+ }
218
+ res.json({ ok: true, path: result });
219
+ });
220
+ router.post('/upload', async (req, res) => {
221
+ const ws = fileService.getWorkspace(req.params.id);
222
+ if (!ws) {
223
+ res.status(404).json({ error: 'Workspace not found' });
224
+ return;
225
+ }
226
+ const { targetDir, files } = req.body;
227
+ if (!Array.isArray(files) || files.length === 0) {
228
+ res.status(400).json({ error: 'files are required' });
229
+ return;
230
+ }
231
+ const results = [];
232
+ for (const f of files) {
233
+ if (!f.name || !f.content)
234
+ continue;
235
+ const buffer = Buffer.from(f.content, 'base64');
236
+ const relPath = targetDir ? `${targetDir}/${f.name}` : f.name;
237
+ const ok = await fileService.writeFileBinary(ws, relPath, buffer);
238
+ if (ok)
239
+ results.push(relPath);
240
+ }
241
+ res.json({ ok: true, paths: results });
242
+ });
147
243
  export default router;
148
244
  //# sourceMappingURL=file.js.map
@@ -1,5 +1,5 @@
1
1
  import { Router } from 'express';
2
- import { gitStatus, gitDiff, gitLog, gitCommit, gitDiscard, gitDiscardAll, gitBranches, gitCheckout, gitInit, gitGenerateCommitMsg, gitPush, gitPull, gitGetRemotes, gitAddRemote } from '../adapters/git.js';
2
+ import { gitStatus, gitDiff, gitLog, gitCommit, gitDiscard, gitDiscardAll, gitBranches, gitCheckout, gitInit, gitGenerateCommitMsg, gitPush, gitPull, gitGetRemotes, gitAddRemote, gitCheckoutDetached, gitCherryPick, gitCreateBranch, gitDeleteBranch, gitCreateTag, gitCommitDiff, gitGetRemoteUrl, gitMergeBase, gitGetConfig, gitSetConfig } from '../adapters/git.js';
3
3
  const router = Router({ mergeParams: true });
4
4
  router.get('/status', async (req, res) => {
5
5
  try {
@@ -149,5 +149,126 @@ router.post('/remotes', async (req, res) => {
149
149
  res.status(500).json({ error: err.message });
150
150
  }
151
151
  });
152
+ router.post('/checkout-detached', async (req, res) => {
153
+ try {
154
+ const { commitHash } = req.body;
155
+ if (!commitHash) {
156
+ res.status(400).json({ error: 'commitHash is required' });
157
+ return;
158
+ }
159
+ await gitCheckoutDetached(req.params.id, commitHash);
160
+ res.json({ ok: true });
161
+ }
162
+ catch (err) {
163
+ res.status(500).json({ error: err.message });
164
+ }
165
+ });
166
+ router.post('/cherry-pick', async (req, res) => {
167
+ try {
168
+ const { commitHash } = req.body;
169
+ if (!commitHash) {
170
+ res.status(400).json({ error: 'commitHash is required' });
171
+ return;
172
+ }
173
+ await gitCherryPick(req.params.id, commitHash);
174
+ res.json({ ok: true });
175
+ }
176
+ catch (err) {
177
+ res.status(500).json({ error: err.message });
178
+ }
179
+ });
180
+ router.post('/create-branch', async (req, res) => {
181
+ try {
182
+ const { name, startPoint } = req.body;
183
+ if (!name) {
184
+ res.status(400).json({ error: 'name is required' });
185
+ return;
186
+ }
187
+ await gitCreateBranch(req.params.id, name, startPoint);
188
+ res.json({ ok: true });
189
+ }
190
+ catch (err) {
191
+ res.status(500).json({ error: err.message });
192
+ }
193
+ });
194
+ router.post('/delete-branch', async (req, res) => {
195
+ try {
196
+ const { name, force } = req.body;
197
+ if (!name) {
198
+ res.status(400).json({ error: 'name is required' });
199
+ return;
200
+ }
201
+ await gitDeleteBranch(req.params.id, name, force);
202
+ res.json({ ok: true });
203
+ }
204
+ catch (err) {
205
+ res.status(500).json({ error: err.message });
206
+ }
207
+ });
208
+ router.post('/create-tag', async (req, res) => {
209
+ try {
210
+ const { name, commitHash } = req.body;
211
+ if (!name) {
212
+ res.status(400).json({ error: 'name is required' });
213
+ return;
214
+ }
215
+ await gitCreateTag(req.params.id, name, commitHash);
216
+ res.json({ ok: true });
217
+ }
218
+ catch (err) {
219
+ res.status(500).json({ error: err.message });
220
+ }
221
+ });
222
+ router.get('/commit-diff', async (req, res) => {
223
+ try {
224
+ const hash = req.query.hash;
225
+ if (!hash) {
226
+ res.status(400).json({ error: 'hash is required' });
227
+ return;
228
+ }
229
+ const diffs = await gitCommitDiff(req.params.id, hash);
230
+ res.json(diffs);
231
+ }
232
+ catch (err) {
233
+ res.status(500).json({ error: err.message });
234
+ }
235
+ });
236
+ router.get('/remote-url', async (req, res) => {
237
+ try {
238
+ const url = await gitGetRemoteUrl(req.params.id);
239
+ res.json({ url });
240
+ }
241
+ catch (err) {
242
+ res.status(500).json({ error: err.message });
243
+ }
244
+ });
245
+ router.get('/merge-base', async (req, res) => {
246
+ try {
247
+ const hash = await gitMergeBase(req.params.id);
248
+ res.json({ hash });
249
+ }
250
+ catch (err) {
251
+ res.status(500).json({ error: err.message });
252
+ }
253
+ });
254
+ router.get('/config', async (req, res) => {
255
+ try {
256
+ const config = await gitGetConfig('local', req.params.id);
257
+ res.json(config);
258
+ }
259
+ catch (err) {
260
+ res.status(500).json({ error: err.message });
261
+ }
262
+ });
263
+ router.post('/config', async (req, res) => {
264
+ try {
265
+ const { name, email, proxy } = req.body;
266
+ await gitSetConfig('local', { name, email, proxy }, req.params.id);
267
+ res.json({ ok: true });
268
+ }
269
+ catch (err) {
270
+ res.status(500).json({ error: err.message });
271
+ }
272
+ });
152
273
  export default router;
153
274
  //# sourceMappingURL=git.js.map
@@ -0,0 +1,33 @@
1
+ import { Router } from 'express';
2
+ import * as store from '../storage/iframe-bookmarks-store.js';
3
+ const router = Router();
4
+ router.get('/', (_req, res) => {
5
+ res.json(store.listBookmarks());
6
+ });
7
+ router.post('/', (req, res) => {
8
+ const { title, url, size } = req.body;
9
+ if (!title || !url) {
10
+ res.status(400).json({ error: 'title and url are required' });
11
+ return;
12
+ }
13
+ res.status(201).json(store.addBookmark({ title, url, size }));
14
+ });
15
+ router.put('/:id', (req, res) => {
16
+ const { title, url, size } = req.body;
17
+ const updated = store.updateBookmark(req.params.id, { title, url, size });
18
+ if (!updated) {
19
+ res.status(404).json({ error: 'bookmark not found' });
20
+ return;
21
+ }
22
+ res.json(updated);
23
+ });
24
+ router.delete('/:id', (req, res) => {
25
+ const ok = store.removeBookmark(req.params.id);
26
+ if (!ok) {
27
+ res.status(404).json({ error: 'bookmark not found' });
28
+ return;
29
+ }
30
+ res.json({ ok: true });
31
+ });
32
+ export default router;
33
+ //# sourceMappingURL=iframe-bookmarks.js.map
@@ -5,6 +5,7 @@ import * as channelService from '../services/channel.js';
5
5
  import { broadcastToWorkspace } from '../ws/connection-manager.js';
6
6
  import { stopChannelRuns } from '../ws/agent-runner.js';
7
7
  import * as agentService from '../services/agent.js';
8
+ import * as taskService from '../services/task.js';
8
9
  import { retryIssue } from '../services/issue-retry.js';
9
10
  import { hasActiveIssueAutomation, runIssueAutomation } from '../agents/issue-agent-runner.js';
10
11
  import * as workflowService from '../services/workflow.js';
@@ -83,6 +84,17 @@ router.post('/:issueId/start', (req, res) => {
83
84
  const workspaceId = req.params.id;
84
85
  const { issueId } = req.params;
85
86
  const before = issueService.getById(workspaceId, issueId);
87
+ if (!before) {
88
+ res.status(404).json({ error: 'issue not found' });
89
+ return;
90
+ }
91
+ // Pre-check: must have workflow or existing tasks
92
+ const hasWorkflow = Boolean(before.workflowId);
93
+ const existingTasks = taskService.list(workspaceId, issueId);
94
+ if (!hasWorkflow && existingTasks.length === 0) {
95
+ res.status(400).json({ error: 'No workflow or tasks configured for this issue' });
96
+ return;
97
+ }
86
98
  const issue = issueService.updateStatus(workspaceId, issueId, 'planned');
87
99
  if (!issue) {
88
100
  res.status(404).json({ error: 'issue not found' });
@@ -0,0 +1,99 @@
1
+ import { Router } from 'express';
2
+ import * as store from '../storage/speech-recognition-store.js';
3
+ import { createSpeechSession } from '../services/speech-recognition/index.js';
4
+ const router = Router();
5
+ // REST: CRUD for speech recognition configs
6
+ router.get('/', (_req, res) => {
7
+ res.json(store.listSpeechConfigs());
8
+ });
9
+ router.post('/', (req, res) => {
10
+ const { provider, label, credentials } = req.body;
11
+ if (!provider || !credentials) {
12
+ res.status(400).json({ error: 'provider and credentials are required' });
13
+ return;
14
+ }
15
+ const item = store.createSpeechConfig({ provider, label: label || provider, credentials });
16
+ res.status(201).json(item);
17
+ });
18
+ router.put('/:id', (req, res) => {
19
+ const { label, credentials, enabled } = req.body;
20
+ const updated = store.updateSpeechConfig(req.params.id, { label, credentials, enabled });
21
+ if (!updated) {
22
+ res.status(404).json({ error: 'Config not found' });
23
+ return;
24
+ }
25
+ res.json(updated);
26
+ });
27
+ router.delete('/:id', (req, res) => {
28
+ if (!store.deleteSpeechConfig(req.params.id)) {
29
+ res.status(404).json({ error: 'Config not found' });
30
+ return;
31
+ }
32
+ res.status(204).end();
33
+ });
34
+ // Export a handler for WebSocket upgrade (speech streaming)
35
+ export async function handleSpeechStream(clientWs, configId) {
36
+ console.log('[speech-ws] new connection, configId:', configId || '(default)');
37
+ const config = configId
38
+ ? store.getSpeechConfig(configId)
39
+ : store.getDefaultSpeechConfig();
40
+ if (!config) {
41
+ console.warn('[speech-ws] no config found, closing');
42
+ clientWs.close(4004, 'No speech recognition config found');
43
+ return;
44
+ }
45
+ console.log('[speech-ws] using config:', config.id, 'provider:', config.provider);
46
+ try {
47
+ console.log('[speech-ws] creating session...');
48
+ const session = await createSpeechSession(config);
49
+ console.log('[speech-ws] session created');
50
+ let audioPackets = 0;
51
+ let lastLogTime = Date.now();
52
+ session.onResult((result) => {
53
+ console.log('[speech-ws] result:', result.text, 'isFinal:', result.isFinal);
54
+ if (clientWs.readyState === 1) {
55
+ clientWs.send(JSON.stringify(result));
56
+ }
57
+ });
58
+ session.onError((err) => {
59
+ console.error('[speech-ws] session error:', err.message);
60
+ if (clientWs.readyState === 1) {
61
+ clientWs.send(JSON.stringify({ error: err.message }));
62
+ }
63
+ });
64
+ session.onClose(() => {
65
+ console.log('[speech-ws] session closed, total audio packets:', audioPackets);
66
+ if (clientWs.readyState === 1) {
67
+ clientWs.close(1000, 'Session ended');
68
+ }
69
+ });
70
+ clientWs.on('message', (data) => {
71
+ if (Buffer.isBuffer(data)) {
72
+ session.sendAudio(data);
73
+ audioPackets++;
74
+ const now = Date.now();
75
+ if (audioPackets <= 3 || now - lastLogTime > 5000) {
76
+ console.log('[speech-ws] audio packet #', audioPackets, 'size:', data.byteLength);
77
+ lastLogTime = now;
78
+ }
79
+ }
80
+ else {
81
+ const msg = data.toString();
82
+ console.log('[speech-ws] text message:', msg);
83
+ if (msg === '{"type":"end"}' || msg === '{"type":"end"}') {
84
+ session.end();
85
+ }
86
+ }
87
+ });
88
+ clientWs.on('close', () => {
89
+ console.log('[speech-ws] client disconnected, total audio packets:', audioPackets);
90
+ session.end();
91
+ });
92
+ }
93
+ catch (err) {
94
+ console.error('[speech-ws] failed to create session:', err.message);
95
+ clientWs.close(1011, err.message || 'Failed to create speech session');
96
+ }
97
+ }
98
+ export default router;
99
+ //# sourceMappingURL=speech-recognition.js.map