@agent-spaces/server 0.1.2 → 0.2.1

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 (216) 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 +10 -0
  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/MpJepsgfsHGGcc7rwFU8Y/_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/09_ki3dc5cfwv.css +1 -0
  57. package/dist/web/_next/static/chunks/09jryrjps0vl2.js +1 -0
  58. package/dist/web/_next/static/chunks/0b2tump5duj9j.js +1 -0
  59. package/dist/web/_next/static/chunks/0dctkv_2d2r0g.js +8 -0
  60. package/dist/web/_next/static/chunks/0efgv37kxyht8.js +1 -0
  61. package/dist/web/_next/static/chunks/0fwvdy-ml8wpk.js +1 -0
  62. package/dist/web/_next/static/chunks/0g4vm6.v0o_lt.js +1 -0
  63. package/dist/web/_next/static/chunks/0g_b4t~000.o~.js +179 -0
  64. package/dist/web/_next/static/chunks/0gyede80jpy3n.js +4 -0
  65. package/dist/web/_next/static/chunks/0h256h-tu4e0r.js +2 -0
  66. package/dist/web/_next/static/chunks/0ib18ul605e~a.js +1 -0
  67. package/dist/web/_next/static/chunks/0j1g_rd9t5ot1.js +1 -0
  68. package/dist/web/_next/static/chunks/0jn~llaqcxo4q.js +1 -0
  69. package/dist/web/_next/static/chunks/0jvmviuftg5e2.css +1 -0
  70. package/dist/web/_next/static/chunks/0lb8l2~6s_owo.js +1 -0
  71. package/dist/web/_next/static/chunks/0ni5ot2603_28.js +1 -0
  72. package/dist/web/_next/static/chunks/0pep4mkvt3.rh.js +31 -0
  73. package/dist/web/_next/static/chunks/0pq2670_ezbcj.js +1 -0
  74. package/dist/web/_next/static/chunks/0q_scqkk9.t53.js +2 -0
  75. package/dist/web/_next/static/chunks/0rrdur.v1a5r7.js +1 -0
  76. package/dist/web/_next/static/chunks/0spo.tmfeas-o.js +1 -0
  77. package/dist/web/_next/static/chunks/0tta_56qj1z8-.js +1 -0
  78. package/dist/web/_next/static/chunks/0u88ij9dqqh~-.js +1 -0
  79. package/dist/web/_next/static/chunks/0zl19l5tuoppw.js +1 -0
  80. package/dist/web/_next/static/chunks/11n16hogah-5..js +1 -0
  81. package/dist/web/_next/static/chunks/1250wo~-5dgyx.js +1 -0
  82. package/dist/web/_next/static/chunks/14n8i2xz4_y-e.js +1 -0
  83. package/dist/web/_next/static/chunks/160ji-.dfvm20.js +1 -0
  84. package/dist/web/_next/static/chunks/turbopack-0lxiiw.jhevml.js +1 -0
  85. package/dist/web/_next/static/media/favicon.0~ekuj.zhggpa.ico +0 -0
  86. package/dist/web/_not-found/__next._full.txt +24 -19
  87. package/dist/web/_not-found/__next._head.txt +4 -4
  88. package/dist/web/_not-found/__next._index.txt +9 -8
  89. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  90. package/dist/web/_not-found/__next._not-found.txt +3 -3
  91. package/dist/web/_not-found/__next._tree.txt +2 -2
  92. package/dist/web/_not-found.html +1 -1
  93. package/dist/web/_not-found.txt +24 -19
  94. package/dist/web/apple-touch-icon.png +0 -0
  95. package/dist/web/favicon.ico +0 -0
  96. package/dist/web/icon-192.png +0 -0
  97. package/dist/web/icon-512.png +0 -0
  98. package/dist/web/index.html +1 -1
  99. package/dist/web/index.txt +22 -21
  100. package/dist/web/login/__next._full.txt +24 -23
  101. package/dist/web/login/__next._head.txt +4 -4
  102. package/dist/web/login/__next._index.txt +9 -8
  103. package/dist/web/login/__next._tree.txt +2 -2
  104. package/dist/web/login/__next.login.__PAGE__.txt +4 -4
  105. package/dist/web/login/__next.login.txt +3 -3
  106. package/dist/web/login.html +1 -1
  107. package/dist/web/login.txt +24 -23
  108. package/dist/web/settings/__next._full.txt +34 -0
  109. package/dist/web/settings/__next._head.txt +6 -0
  110. package/dist/web/settings/__next._index.txt +11 -0
  111. package/dist/web/settings/__next._tree.txt +8 -0
  112. package/dist/web/settings/__next.settings.__PAGE__.txt +9 -0
  113. package/dist/web/settings/__next.settings.txt +7 -0
  114. package/dist/web/settings/agents/__next._full.txt +38 -0
  115. package/dist/web/settings/agents/__next._head.txt +6 -0
  116. package/dist/web/settings/agents/__next._index.txt +11 -0
  117. package/dist/web/settings/agents/__next._tree.txt +8 -0
  118. package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +9 -0
  119. package/dist/web/settings/agents/__next.settings.agents.txt +5 -0
  120. package/dist/web/settings/agents/__next.settings.txt +7 -0
  121. package/dist/web/settings/agents.html +1 -0
  122. package/dist/web/settings/agents.txt +38 -0
  123. package/dist/web/settings/mcps/__next._full.txt +38 -0
  124. package/dist/web/settings/mcps/__next._head.txt +6 -0
  125. package/dist/web/settings/mcps/__next._index.txt +11 -0
  126. package/dist/web/settings/mcps/__next._tree.txt +8 -0
  127. package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +9 -0
  128. package/dist/web/settings/mcps/__next.settings.mcps.txt +5 -0
  129. package/dist/web/settings/mcps/__next.settings.txt +7 -0
  130. package/dist/web/settings/mcps.html +1 -0
  131. package/dist/web/settings/mcps.txt +38 -0
  132. package/dist/web/settings/models/__next._full.txt +38 -0
  133. package/dist/web/settings/models/__next._head.txt +6 -0
  134. package/dist/web/settings/models/__next._index.txt +11 -0
  135. package/dist/web/settings/models/__next._tree.txt +8 -0
  136. package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +9 -0
  137. package/dist/web/settings/models/__next.settings.models.txt +5 -0
  138. package/dist/web/settings/models/__next.settings.txt +7 -0
  139. package/dist/web/settings/models.html +1 -0
  140. package/dist/web/settings/models.txt +38 -0
  141. package/dist/web/settings/providers/__next._full.txt +38 -0
  142. package/dist/web/settings/providers/__next._head.txt +6 -0
  143. package/dist/web/settings/providers/__next._index.txt +11 -0
  144. package/dist/web/settings/providers/__next._tree.txt +8 -0
  145. package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +9 -0
  146. package/dist/web/settings/providers/__next.settings.providers.txt +5 -0
  147. package/dist/web/settings/providers/__next.settings.txt +7 -0
  148. package/dist/web/settings/providers.html +1 -0
  149. package/dist/web/settings/providers.txt +38 -0
  150. package/dist/web/settings/skills/__next._full.txt +38 -0
  151. package/dist/web/settings/skills/__next._head.txt +6 -0
  152. package/dist/web/settings/skills/__next._index.txt +11 -0
  153. package/dist/web/settings/skills/__next._tree.txt +8 -0
  154. package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +9 -0
  155. package/dist/web/settings/skills/__next.settings.skills.txt +5 -0
  156. package/dist/web/settings/skills/__next.settings.txt +7 -0
  157. package/dist/web/settings/skills.html +1 -0
  158. package/dist/web/settings/skills.txt +38 -0
  159. package/dist/web/settings.html +1 -0
  160. package/dist/web/settings.txt +34 -0
  161. package/dist/web/workflows/__next._full.txt +35 -0
  162. package/dist/web/workflows/__next._head.txt +6 -0
  163. package/dist/web/workflows/__next._index.txt +11 -0
  164. package/dist/web/workflows/__next._tree.txt +9 -0
  165. package/dist/web/workflows/__next.workflows.__PAGE__.txt +10 -0
  166. package/dist/web/workflows/__next.workflows.txt +5 -0
  167. package/dist/web/workflows.html +1 -0
  168. package/dist/web/workflows.txt +35 -0
  169. package/dist/web/workspace/_/__next._full.txt +25 -20
  170. package/dist/web/workspace/_/__next._head.txt +4 -4
  171. package/dist/web/workspace/_/__next._index.txt +9 -8
  172. package/dist/web/workspace/_/__next._tree.txt +2 -2
  173. package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
  174. package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
  175. package/dist/web/workspace/_/__next.workspace.txt +3 -3
  176. package/dist/web/workspace/_.html +1 -1
  177. package/dist/web/workspace/_.txt +25 -20
  178. package/dist/web/workspaces/__next._full.txt +24 -23
  179. package/dist/web/workspaces/__next._head.txt +4 -4
  180. package/dist/web/workspaces/__next._index.txt +9 -8
  181. package/dist/web/workspaces/__next._tree.txt +2 -2
  182. package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
  183. package/dist/web/workspaces/__next.workspaces.txt +3 -3
  184. package/dist/web/workspaces.html +1 -1
  185. package/dist/web/workspaces.txt +24 -23
  186. package/dist/ws/agent-prompt.js +84 -0
  187. package/dist/ws/agent-runner.js +592 -0
  188. package/dist/ws/handler.js +9 -1200
  189. package/dist/ws/html-utils.js +30 -0
  190. package/dist/ws/message-parts.js +498 -0
  191. package/package.json +3 -2
  192. package/dist/web/_next/static/chunks/038whpa0zpnas.js +0 -1
  193. package/dist/web/_next/static/chunks/06q5go~xoz5a3.js +0 -1
  194. package/dist/web/_next/static/chunks/095.wizobwtyg.js +0 -2
  195. package/dist/web/_next/static/chunks/0bfg.w~u-83h5.js +0 -1
  196. package/dist/web/_next/static/chunks/0ce7i6~sb20rv.js +0 -113
  197. package/dist/web/_next/static/chunks/0d4~pcva1uk-a.js +0 -1
  198. package/dist/web/_next/static/chunks/0iq70n_u75nyu.js +0 -1
  199. package/dist/web/_next/static/chunks/0memz-8zsbxpu.js +0 -1
  200. package/dist/web/_next/static/chunks/0nw~w.3~twebx.js +0 -2
  201. package/dist/web/_next/static/chunks/0pyytgz~5vt0f.js +0 -31
  202. package/dist/web/_next/static/chunks/0ujjcrz~1nq.q.css +0 -1
  203. package/dist/web/_next/static/chunks/0vdnx9n41dyjl.js +0 -1
  204. package/dist/web/_next/static/chunks/0yl4mqmxtll6g.js +0 -1
  205. package/dist/web/_next/static/chunks/0zcbfka5tcle3.js +0 -1
  206. package/dist/web/_next/static/chunks/0~eo58u99i.fr.js +0 -8
  207. package/dist/web/_next/static/chunks/13_2vxyccsv84.js +0 -4
  208. package/dist/web/_next/static/chunks/13wfs~urgxu0w.js +0 -1
  209. package/dist/web/_next/static/chunks/157~3c6wqkt83.js +0 -1
  210. package/dist/web/_next/static/chunks/15jvow_z4uq-..js +0 -1
  211. package/dist/web/_next/static/chunks/15v697nf~r-cy.js +0 -2
  212. package/dist/web/_next/static/chunks/turbopack-164~7ulq9o9yc.js +0 -1
  213. package/dist/web/_next/static/media/favicon.0x3dzn~oxb6tn.ico +0 -0
  214. package/dist/web/_next/static/owMnKmxATbtXK_J_k_uHh/_buildManifest.js +0 -21
  215. /package/dist/web/_next/static/{owMnKmxATbtXK_J_k_uHh → MpJepsgfsHGGcc7rwFU8Y}/_clientMiddlewareManifest.js +0 -0
  216. /package/dist/web/_next/static/{owMnKmxATbtXK_J_k_uHh → MpJepsgfsHGGcc7rwFU8Y}/_ssgManifest.js +0 -0
@@ -0,0 +1,592 @@
1
+ import { broadcastToWorkspace } from './connection-manager.js';
2
+ import { createMessage, updateMessage, listMessages } from '../services/message.js';
3
+ import { getChannel, updateChannel } from '../services/channel.js';
4
+ import * as issueService from '../services/issue.js';
5
+ import { createIssueFunctionTools, createCommandFunctionTools } from '../services/builtin-tools.js';
6
+ import { startScheduler } from '../agents/scheduler-agent.js';
7
+ import * as agentService from '../services/agent.js';
8
+ import * as wsService from '../services/workspace.js';
9
+ import { createAgentRuntime } from '../adapters/agent-runtime.js';
10
+ import { saveToolDetails } from '../services/tool-detail.js';
11
+ import { getThinkingRuntimeConfig } from '../services/llm-model-config.js';
12
+ import { buildAgentMessageParts, normalizeOutputLines, mergeRuntimeOutput, buildToolDetailId, summarizeToolLine, findToolDetailForResult } from './message-parts.js';
13
+ import { buildAgentPrompt, buildBuiltInTools } from './agent-prompt.js';
14
+ // --- State ---
15
+ const activeSchedulers = new Set();
16
+ const activeChannelRuns = new Map();
17
+ const pendingQuestionRuns = new Map();
18
+ // --- Public API ---
19
+ export function ensureScheduler(workspaceId, ctx) {
20
+ if (!activeSchedulers.has(workspaceId)) {
21
+ activeSchedulers.add(workspaceId);
22
+ setTimeout(() => {
23
+ startScheduler(workspaceId, ctx);
24
+ console.log(`[ws] scheduler started for workspace ${workspaceId}`);
25
+ }, 10_000);
26
+ console.log(`[ws] scheduler scheduled for workspace ${workspaceId} in 10000ms`);
27
+ }
28
+ }
29
+ export function makeContext(workspaceId) {
30
+ return {
31
+ workspaceId,
32
+ broadcast: (event, data) => broadcastToWorkspace(workspaceId, event, data),
33
+ getSession: (sessionId) => agentService.getById(workspaceId, sessionId),
34
+ updateSessionStatus: (sessionId, status, extra) => agentService.updateStatus(workspaceId, sessionId, status, extra),
35
+ };
36
+ }
37
+ export function stopChannelRuns(workspaceId, channelId) {
38
+ const runs = activeChannelRuns.get(channelRunKey(workspaceId, channelId));
39
+ if (!runs || runs.size === 0) {
40
+ markInactiveChannelRunsStopped(workspaceId, channelId);
41
+ return;
42
+ }
43
+ for (const run of runs.values()) {
44
+ run.stopped = true;
45
+ run.runtime.stop();
46
+ agentService.complete(workspaceId, run.agentId, 'Stopped by user');
47
+ broadcastToWorkspace(workspaceId, 'agent.status_changed', {
48
+ agentId: run.agentId,
49
+ from: 'active',
50
+ to: 'crashed',
51
+ });
52
+ broadcastToWorkspace(workspaceId, 'agent.error', {
53
+ agentId: run.agentId,
54
+ error: 'Stopped by user',
55
+ });
56
+ const message = updateMessage(workspaceId, channelId, run.messageId, {
57
+ content: 'Stopped by user',
58
+ status: 'error',
59
+ parts: [
60
+ {
61
+ id: `terminal-stopped-${run.agentId}`,
62
+ type: 'terminal',
63
+ output: 'Stopped by user',
64
+ status: 'error',
65
+ },
66
+ ],
67
+ });
68
+ if (message)
69
+ broadcastToWorkspace(workspaceId, 'channel.message.updated', message);
70
+ }
71
+ }
72
+ export function hasActiveChannelRuns(workspaceId, channelId) {
73
+ return Boolean(activeChannelRuns.get(channelRunKey(workspaceId, channelId))?.size);
74
+ }
75
+ export function markInactiveChannelRunsStopped(workspaceId, channelId) {
76
+ if (hasActiveChannelRuns(workspaceId, channelId))
77
+ return [];
78
+ const stopped = [];
79
+ for (const message of listMessages(workspaceId, channelId)) {
80
+ if (message.status !== 'pending' && message.status !== 'streaming')
81
+ continue;
82
+ const updated = updateMessage(workspaceId, channelId, message.id, {
83
+ content: message.content || 'Stopped by user',
84
+ status: 'error',
85
+ parts: message.parts?.map((part) => {
86
+ if ('status' in part && part.status === 'streaming') {
87
+ return { ...part, status: 'completed' };
88
+ }
89
+ return part;
90
+ }),
91
+ });
92
+ if (updated) {
93
+ stopped.push(updated);
94
+ broadcastToWorkspace(workspaceId, 'channel.message.updated', updated);
95
+ }
96
+ }
97
+ return stopped;
98
+ }
99
+ export function handleAnswerQuestion(_ws, workspaceId, data) {
100
+ const { channelId, messageId, questionId, answer } = data;
101
+ const trimmed = answer?.trim();
102
+ if (!channelId || !messageId || !questionId || !trimmed)
103
+ return;
104
+ const message = listMessages(workspaceId, channelId).find((item) => item.id === messageId);
105
+ if (!message)
106
+ return;
107
+ const updatedParts = message.parts?.map((part) => {
108
+ if (part.type !== 'ask_user_question' || part.id !== questionId)
109
+ return part;
110
+ return { ...part, status: 'answered', answer: trimmed };
111
+ });
112
+ const updated = updateMessage(workspaceId, channelId, messageId, {
113
+ status: 'streaming',
114
+ parts: updatedParts,
115
+ });
116
+ if (updated)
117
+ broadcastToWorkspace(workspaceId, 'channel.message.updated', updated);
118
+ const key = questionRunKey(workspaceId, channelId, messageId, questionId);
119
+ const pending = pendingQuestionRuns.get(key);
120
+ pendingQuestionRuns.delete(key);
121
+ if (!pending)
122
+ return;
123
+ const seedQuestion = questionFromAnsweredPart(updatedParts, questionId);
124
+ void runMentionedAgent(workspaceId, channelId, pending.agentConfigId, [
125
+ 'The user answered your previous question.',
126
+ `Question: ${pending.question}`,
127
+ `Answer: ${trimmed}`,
128
+ 'Continue from this answer and complete the original task.',
129
+ ].join('\n'), {
130
+ messageId,
131
+ seedOutput: normalizeOutputLines([message.content]),
132
+ seedQuestions: seedQuestion ? [seedQuestion] : [],
133
+ });
134
+ }
135
+ export async function runMentionedAgent(workspaceId, channelId, agentConfigId, prompt, options = {}) {
136
+ const preset = agentService.listPresets(workspaceId)?.find((agent) => agent.id === agentConfigId);
137
+ if (!preset || preset.enabled === false)
138
+ return;
139
+ const session = agentService.create(workspaceId, preset.role, preset.id);
140
+ broadcastToWorkspace(workspaceId, 'agent.started', session);
141
+ agentService.updateStatus(workspaceId, session.id, 'active');
142
+ broadcastToWorkspace(workspaceId, 'agent.status_changed', {
143
+ agentId: session.id,
144
+ from: 'idle',
145
+ to: 'active',
146
+ });
147
+ const configDir = agentService.getAgentConfigDir(workspaceId, preset);
148
+ const mcpServers = agentService.getMcpServers(preset.mcps);
149
+ const skills = agentService.getAvailableSkillNames(configDir, preset.skills);
150
+ const workspace = wsService.getById(workspaceId);
151
+ const { channel, issue } = resolveIssueChannelContext(workspaceId, channelId);
152
+ const functionTools = [
153
+ ...createIssueFunctionTools(workspaceId, channel, {
154
+ senderId: preset.id,
155
+ senderRole: preset.role,
156
+ }, preset.tools),
157
+ ...createCommandFunctionTools(workspaceId),
158
+ ];
159
+ const startTime = Date.now();
160
+ const existingMessage = options.messageId
161
+ ? listMessages(workspaceId, channelId).find((message) => message.id === options.messageId)
162
+ : undefined;
163
+ const pending = existingMessage
164
+ ? updateMessage(workspaceId, channelId, existingMessage.id, {
165
+ status: 'streaming',
166
+ metadata: {
167
+ ...existingMessage.metadata,
168
+ agentSessionId: session.id,
169
+ runtime: preset.runtimeKind,
170
+ model: preset.modelId,
171
+ duration: 0,
172
+ },
173
+ }) ?? existingMessage
174
+ : createMessage(workspaceId, channelId, {
175
+ senderId: preset.id,
176
+ senderRole: preset.role,
177
+ content: 'Agent is processing...',
178
+ type: 'text',
179
+ status: 'streaming',
180
+ metadata: {
181
+ agentSessionId: session.id,
182
+ runtime: preset.runtimeKind,
183
+ model: preset.modelId,
184
+ duration: 0,
185
+ },
186
+ parts: [
187
+ {
188
+ id: `reasoning-${session.id}`,
189
+ type: 'reasoning',
190
+ text: 'Preparing agent runtime and loading conversation context...',
191
+ status: 'streaming',
192
+ },
193
+ ],
194
+ });
195
+ broadcastToWorkspace(workspaceId, existingMessage ? 'channel.message.updated' : 'channel.message', pending);
196
+ let activeRun;
197
+ const liveReasoning = [];
198
+ try {
199
+ const runtime = createAgentRuntime({
200
+ kind: preset.runtimeKind,
201
+ provider: preset.modelProvider,
202
+ model: preset.modelId,
203
+ apiKey: preset.apiKey,
204
+ baseURL: getRuntimeBaseURL(preset.modelProvider, preset.apiBase),
205
+ adapterBaseURL: preset.apiBase,
206
+ ...getThinkingRuntimeConfig(preset),
207
+ });
208
+ activeRun = {
209
+ agentId: session.id,
210
+ agentConfigId,
211
+ messageId: pending.id,
212
+ runtime,
213
+ };
214
+ trackChannelRun(workspaceId, channelId, activeRun);
215
+ const history = listMessages(workspaceId, channelId, { limit: 20 });
216
+ const liveOutput = [...(options.seedOutput ?? [])];
217
+ const askUserQuestions = [...(options.seedQuestions ?? [])];
218
+ const toolDetails = new Map();
219
+ const toolUseDetailIds = new Map();
220
+ let lastLiveUpdate = 0;
221
+ const broadcastLiveParts = (force = false) => {
222
+ const now = Date.now();
223
+ if (!force && now - lastLiveUpdate < 120)
224
+ return;
225
+ lastLiveUpdate = now;
226
+ const parts = buildAgentMessageParts({
227
+ sessionId: session.id,
228
+ workspaceRoot: workspace?.boundDirs?.[0],
229
+ presetName: preset.name || preset.role,
230
+ role: preset.role,
231
+ model: preset.modelId,
232
+ systemPrompt: preset.systemPrompt,
233
+ mcpServers: Object.keys(mcpServers ?? {}),
234
+ skills,
235
+ builtInTools: buildBuiltInTools(functionTools, channel, issue),
236
+ output: liveOutput,
237
+ reasoning: liveReasoning,
238
+ toolDetails,
239
+ askUserQuestions,
240
+ success: true,
241
+ });
242
+ if (parts.length === 0)
243
+ return;
244
+ const status = askUserQuestions.some((question) => !question.answer) ? 'waiting_for_user' : 'streaming';
245
+ const live = updateMessage(workspaceId, channelId, pending.id, {
246
+ content: liveOutput.join('\n') || pending.content,
247
+ status,
248
+ parts,
249
+ metadata: {
250
+ ...pending.metadata,
251
+ duration: Date.now() - startTime,
252
+ },
253
+ });
254
+ if (live)
255
+ broadcastToWorkspace(workspaceId, 'channel.message.updated', live);
256
+ };
257
+ const result = await runtime.execute(buildAgentPrompt(workspaceId, preset.systemPrompt, prompt, history, {
258
+ mcpServers: Object.keys(mcpServers ?? {}),
259
+ skills,
260
+ boundDirs: workspace?.boundDirs,
261
+ builtInTools: buildBuiltInTools(functionTools, channel, issue),
262
+ }), agentService.resolveWorkingDir(workspaceId, preset), {
263
+ maxTurns: 100,
264
+ functionTools,
265
+ mcpServers,
266
+ skills,
267
+ configDir,
268
+ sandboxDirs: preset.sandboxDirs,
269
+ onEvent: (event) => {
270
+ if (event.type === 'reasoning') {
271
+ liveReasoning.push({ text: event.text, status: event.status });
272
+ broadcastToWorkspace(workspaceId, 'agent.output', { agentId: session.id, data: event.text });
273
+ broadcastLiveParts();
274
+ return;
275
+ }
276
+ if (event.type === 'tool_use') {
277
+ if (event.name === 'AskUserQuestion') {
278
+ const question = parseAskUserQuestion(event.id, event.input);
279
+ askUserQuestions.push(question);
280
+ pendingQuestionRuns.set(questionRunKey(workspaceId, channelId, pending.id, question.id), {
281
+ agentConfigId,
282
+ question: question.question,
283
+ });
284
+ broadcastLiveParts(true);
285
+ return;
286
+ }
287
+ // Intercept TodoWrite to persist todos to channel
288
+ if (event.name === 'TodoWrite' && event.input && typeof event.input === 'object') {
289
+ const input = event.input;
290
+ if (Array.isArray(input.todos)) {
291
+ const todos = input.todos.map((t, index) => ({
292
+ id: String(t.id ?? t.activeForm ?? t.subject ?? t.title ?? t.content ?? index),
293
+ subject: String(t.subject ?? t.title ?? t.activeForm ?? t.content ?? ''),
294
+ description: t.description || t.content ? String(t.description ?? t.content) : undefined,
295
+ status: (['pending', 'in_progress', 'completed'].includes(t.status) ? t.status : 'pending'),
296
+ activeForm: t.activeForm ? String(t.activeForm) : undefined,
297
+ }));
298
+ const updated = updateChannel(workspaceId, channelId, { todos });
299
+ if (updated)
300
+ broadcastToWorkspace(workspaceId, 'channel.updated', updated);
301
+ }
302
+ }
303
+ const detailId = buildToolDetailId(event.id, event.line);
304
+ toolUseDetailIds.set(event.id, detailId);
305
+ toolDetails.set(detailId, {
306
+ id: detailId,
307
+ workspaceId,
308
+ channelId,
309
+ messageId: pending.id,
310
+ title: summarizeToolLine(event.line, workspace?.boundDirs?.[0]).title,
311
+ raw: event.line,
312
+ input: event.input,
313
+ createdAt: new Date().toISOString(),
314
+ });
315
+ saveToolDetails(workspaceId, channelId, Array.from(toolDetails.values()));
316
+ liveOutput.push(event.line);
317
+ broadcastToWorkspace(workspaceId, 'agent.output', { agentId: session.id, data: event.line });
318
+ broadcastLiveParts();
319
+ return;
320
+ }
321
+ if (event.type === 'tool_result') {
322
+ const askedQuestion = event.toolUseId
323
+ ? askUserQuestions.find((question) => question.toolUseId === event.toolUseId)
324
+ : undefined;
325
+ if (askedQuestion)
326
+ return;
327
+ const detail = findToolDetailForResult(event.toolUseId, event.result, toolUseDetailIds, toolDetails, workspace?.boundDirs?.[0]);
328
+ if (detail && isAgentSpacesIssueTool(detail.raw || detail.title)) {
329
+ const updatedChannel = getChannel(workspaceId, channelId);
330
+ if (updatedChannel)
331
+ broadcastToWorkspace(workspaceId, 'channel.updated', updatedChannel);
332
+ const updatedIssue = updatedChannel?.issueId ? issueService.getById(workspaceId, updatedChannel.issueId) : null;
333
+ if (updatedIssue) {
334
+ broadcastToWorkspace(workspaceId, isCreateCurrentChannelIssueTool(detail.raw || detail.title) ? 'issue.created' : 'issue.updated', updatedIssue);
335
+ }
336
+ }
337
+ if (detail) {
338
+ detail.output = event.result;
339
+ detail.updatedAt = new Date().toISOString();
340
+ saveToolDetails(workspaceId, channelId, [detail]);
341
+ }
342
+ return;
343
+ }
344
+ if (event.type !== 'output')
345
+ return;
346
+ liveOutput.push(event.line);
347
+ broadcastToWorkspace(workspaceId, 'agent.output', { agentId: session.id, data: event.line });
348
+ broadcastLiveParts();
349
+ },
350
+ });
351
+ broadcastLiveParts(true);
352
+ if (activeRun.stopped)
353
+ return;
354
+ const displayOutput = mergeRuntimeOutput(liveOutput, result.output);
355
+ if (shouldWaitForUserAnswer(askUserQuestions, result.summary, result.error, displayOutput)) {
356
+ const waitingOutput = stripAskUserQuestionErrorLines(liveOutput);
357
+ const waiting = updateMessage(workspaceId, channelId, pending.id, {
358
+ content: waitingOutput.join('\n') || pending.content,
359
+ type: 'text',
360
+ status: 'waiting_for_user',
361
+ metadata: {
362
+ agentSessionId: session.id,
363
+ runtime: preset.runtimeKind,
364
+ model: preset.modelId,
365
+ summary: 'Waiting for user answer',
366
+ duration: Date.now() - startTime,
367
+ },
368
+ parts: buildAgentMessageParts({
369
+ sessionId: session.id,
370
+ workspaceRoot: workspace?.boundDirs?.[0],
371
+ presetName: preset.name || preset.role,
372
+ role: preset.role,
373
+ model: preset.modelId,
374
+ systemPrompt: preset.systemPrompt,
375
+ mcpServers: Object.keys(mcpServers ?? {}),
376
+ skills,
377
+ output: waitingOutput,
378
+ reasoning: liveReasoning,
379
+ toolDetails,
380
+ askUserQuestions,
381
+ success: true,
382
+ }),
383
+ });
384
+ if (waiting)
385
+ broadcastToWorkspace(workspaceId, 'channel.message.updated', waiting);
386
+ agentService.updateStatus(workspaceId, session.id, 'blocked');
387
+ broadcastToWorkspace(workspaceId, 'agent.status_changed', {
388
+ agentId: session.id,
389
+ from: 'active',
390
+ to: 'blocked',
391
+ });
392
+ return;
393
+ }
394
+ if (liveOutput.length === 0) {
395
+ for (const line of result.output) {
396
+ broadcastToWorkspace(workspaceId, 'agent.output', { agentId: session.id, data: line });
397
+ }
398
+ }
399
+ agentService.complete(workspaceId, session.id, result.success ? undefined : result.error, {
400
+ runtime: preset.runtimeKind,
401
+ model: preset.modelId,
402
+ summary: result.summary,
403
+ output: displayOutput,
404
+ durationMs: Date.now() - startTime,
405
+ usage: result.usage,
406
+ costUsd: result.costUsd,
407
+ });
408
+ broadcastToWorkspace(workspaceId, 'agent.completed', {
409
+ agentId: session.id,
410
+ result: {
411
+ success: result.success,
412
+ summary: result.summary,
413
+ artifacts: result.artifacts,
414
+ error: result.error,
415
+ },
416
+ error: result.error,
417
+ });
418
+ const reply = updateMessage(workspaceId, channelId, pending.id, {
419
+ content: result.success ? displayOutput.join('\n') : result.error || displayOutput.join('\n'),
420
+ type: 'text',
421
+ status: result.success ? 'completed' : 'error',
422
+ metadata: {
423
+ agentSessionId: session.id,
424
+ runtime: preset.runtimeKind,
425
+ model: preset.modelId,
426
+ summary: result.summary,
427
+ duration: Date.now() - startTime,
428
+ },
429
+ parts: buildAgentMessageParts({
430
+ sessionId: session.id,
431
+ workspaceRoot: workspace?.boundDirs?.[0],
432
+ presetName: preset.name || preset.role,
433
+ role: preset.role,
434
+ model: preset.modelId,
435
+ usage: result.usage,
436
+ systemPrompt: preset.systemPrompt,
437
+ mcpServers: Object.keys(mcpServers ?? {}),
438
+ skills,
439
+ builtInTools: buildBuiltInTools(functionTools, channel, issue),
440
+ output: displayOutput,
441
+ reasoning: liveReasoning,
442
+ toolDetails,
443
+ askUserQuestions,
444
+ success: result.success,
445
+ error: result.error,
446
+ }),
447
+ });
448
+ if (reply)
449
+ broadcastToWorkspace(workspaceId, 'channel.message.updated', reply);
450
+ }
451
+ catch (err) {
452
+ if (activeRun?.stopped)
453
+ return;
454
+ const error = err instanceof Error ? err.message : String(err);
455
+ agentService.complete(workspaceId, session.id, error, {
456
+ runtime: preset.runtimeKind,
457
+ model: preset.modelId,
458
+ summary: error,
459
+ output: [error],
460
+ durationMs: Date.now() - startTime,
461
+ });
462
+ broadcastToWorkspace(workspaceId, 'agent.error', { agentId: session.id, error });
463
+ const reply = updateMessage(workspaceId, channelId, pending.id, {
464
+ content: error,
465
+ type: 'text',
466
+ status: 'error',
467
+ metadata: {
468
+ agentSessionId: session.id,
469
+ runtime: preset.runtimeKind,
470
+ model: preset.modelId,
471
+ duration: Date.now() - startTime,
472
+ },
473
+ parts: buildAgentMessageParts({
474
+ sessionId: session.id,
475
+ workspaceRoot: workspace?.boundDirs?.[0],
476
+ presetName: preset.name || preset.role,
477
+ role: preset.role,
478
+ model: preset.modelId,
479
+ systemPrompt: preset.systemPrompt,
480
+ mcpServers: Object.keys(mcpServers ?? {}),
481
+ skills,
482
+ builtInTools: buildBuiltInTools(functionTools, channel, issue),
483
+ output: [error],
484
+ reasoning: liveReasoning,
485
+ toolDetails: new Map(),
486
+ askUserQuestions: [],
487
+ success: false,
488
+ error,
489
+ }),
490
+ });
491
+ if (reply)
492
+ broadcastToWorkspace(workspaceId, 'channel.message.updated', reply);
493
+ }
494
+ finally {
495
+ untrackChannelRun(workspaceId, channelId, session.id);
496
+ }
497
+ }
498
+ // --- Internal helpers ---
499
+ function getRuntimeBaseURL(provider, apiBase) {
500
+ if (provider === 'openai-responses-to-anthropic-messages'
501
+ || provider === 'openai-chat-completions-to-anthropic-messages')
502
+ return undefined;
503
+ return apiBase;
504
+ }
505
+ function resolveIssueChannelContext(workspaceId, channelId) {
506
+ let channel = getChannel(workspaceId, channelId);
507
+ let issue = channel?.issueId ? issueService.getById(workspaceId, channel.issueId) : null;
508
+ if (issue || channel?.type !== 'issue')
509
+ return { channel, issue };
510
+ issue = issueService.list(workspaceId).find((item) => item.channelId === channelId) ?? null;
511
+ channel = getChannel(workspaceId, channelId);
512
+ return { channel, issue };
513
+ }
514
+ function isAgentSpacesIssueTool(name) {
515
+ return Boolean(name && /(?:agent-spaces\.)?(?:CreateCurrentChannelIssue|ViewCurrentChannelIssue|AddCurrentChannelComment)/.test(name));
516
+ }
517
+ function isCreateCurrentChannelIssueTool(name) {
518
+ return Boolean(name && /(?:agent-spaces\.)?CreateCurrentChannelIssue/.test(name));
519
+ }
520
+ function channelRunKey(workspaceId, channelId) {
521
+ return `${workspaceId}:${channelId}`;
522
+ }
523
+ function trackChannelRun(workspaceId, channelId, run) {
524
+ const key = channelRunKey(workspaceId, channelId);
525
+ const runs = activeChannelRuns.get(key) ?? new Map();
526
+ runs.set(run.agentId, run);
527
+ activeChannelRuns.set(key, runs);
528
+ }
529
+ function untrackChannelRun(workspaceId, channelId, agentId) {
530
+ const key = channelRunKey(workspaceId, channelId);
531
+ const runs = activeChannelRuns.get(key);
532
+ if (!runs)
533
+ return;
534
+ runs.delete(agentId);
535
+ if (runs.size === 0)
536
+ activeChannelRuns.delete(key);
537
+ }
538
+ function questionRunKey(workspaceId, channelId, messageId, questionId) {
539
+ return `${workspaceId}:${channelId}:${messageId}:${questionId}`;
540
+ }
541
+ function parseAskUserQuestion(toolUseId, input) {
542
+ const record = input && typeof input === 'object' ? input : {};
543
+ const rawQuestions = Array.isArray(record.questions) ? record.questions : [];
544
+ const first = rawQuestions.find((item) => item && typeof item === 'object');
545
+ const question = typeof first?.question === 'string' && first.question.trim()
546
+ ? first.question.trim()
547
+ : '请选择一个选项:';
548
+ const options = Array.isArray(first?.options) ? first.options : [];
549
+ const choices = options
550
+ .map((option) => {
551
+ if (typeof option === 'string')
552
+ return option;
553
+ if (!option || typeof option !== 'object')
554
+ return '';
555
+ const value = option;
556
+ return typeof value.label === 'string' ? value.label : '';
557
+ })
558
+ .filter(Boolean);
559
+ return {
560
+ id: `ask-user-${toolUseId}`,
561
+ toolUseId,
562
+ question,
563
+ choices,
564
+ };
565
+ }
566
+ function questionFromAnsweredPart(parts, questionId) {
567
+ const part = parts?.find((item) => item.type === 'ask_user_question' && item.id === questionId);
568
+ if (!part || part.type !== 'ask_user_question')
569
+ return undefined;
570
+ return {
571
+ id: part.id,
572
+ toolUseId: part.toolUseId,
573
+ question: part.question,
574
+ choices: part.choices ?? [],
575
+ answer: part.answer,
576
+ };
577
+ }
578
+ function shouldWaitForUserAnswer(questions, summary, error, output) {
579
+ if (!questions.some((question) => !question.answer))
580
+ return false;
581
+ if (summary === 'Waiting for user answer')
582
+ return true;
583
+ const text = [error ?? '', ...output].join('\n');
584
+ return !text.trim() || isAskUserQuestionError(text);
585
+ }
586
+ function stripAskUserQuestionErrorLines(output) {
587
+ return output.filter((line) => !isAskUserQuestionError(line));
588
+ }
589
+ function isAskUserQuestionError(error) {
590
+ return /Answer questions\?/i.test(error);
591
+ }
592
+ //# sourceMappingURL=agent-runner.js.map