@agent-spaces/server 0.3.67 → 0.4.0
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.
- package/dist/adapters/agent-runtime.js +4 -0
- package/dist/adapters/codex-function-tool-bridge.js +9 -1
- package/dist/adapters/git.js +13 -2
- package/dist/adapters/hermes-runtime.js +711 -108
- package/dist/adapters/langchain-runtime.js +384 -21
- package/dist/adapters/oh-my-pi-runtime.js +858 -0
- package/dist/adapters/open-agent-sdk-runtime.js +202 -5
- package/dist/adapters/open-agent-sdk-runtime.test.js +35 -0
- package/dist/agents/agent-message-parts.js +52 -2
- package/dist/agents/issue-task-controller.js +4 -2
- package/dist/agents/title-generator-agent.js +120 -0
- package/dist/app.js +59 -6
- package/dist/routes/agent-sse.js +70 -7
- package/dist/routes/channel.js +23 -2
- package/dist/routes/chat-run.js +191 -0
- package/dist/routes/chat.js +142 -0
- package/dist/routes/command.js +16 -0
- package/dist/routes/data.js +189 -0
- package/dist/routes/git.js +10 -1
- package/dist/routes/import.js +199 -0
- package/dist/routes/issue.js +16 -4
- package/dist/routes/plugin.js +69 -0
- package/dist/routes/version.js +2 -1
- package/dist/routes/workflow-hook.js +71 -0
- package/dist/routes/workflow.js +282 -7
- package/dist/routes/workspace.js +13 -4
- package/dist/services/agent.js +123 -36
- package/dist/services/ai-text.js +185 -0
- package/dist/services/builtin-tools/index.js +1 -0
- package/dist/services/builtin-tools/workflow-editor-tools.js +509 -0
- package/dist/services/builtin-tools/workflow-exec-tools.js +320 -0
- package/dist/services/chat.js +134 -0
- package/dist/services/command-process-manager.js +16 -0
- package/dist/services/execution-manager.js +1346 -0
- package/dist/services/generated-title.js +59 -0
- package/dist/services/gitignore.js +22 -18
- package/dist/services/interaction-manager.js +114 -0
- package/dist/services/issue-retry.js +25 -0
- package/dist/services/output-style.js +8 -1
- package/dist/services/plugin.js +257 -0
- package/dist/services/prompt-template.js +8 -1
- package/dist/services/pty.js +20 -0
- package/dist/services/search.js +16 -6
- package/dist/services/skill.js +2 -0
- package/dist/services/version.js +17 -7
- package/dist/services/workflow-command-runner.js +5 -4
- package/dist/services/workflow-trigger-service.js +137 -0
- package/dist/services/workflow.js +199 -36
- package/dist/storage/agent-store.js +8 -0
- package/dist/storage/chat-store.js +151 -0
- package/dist/storage/database-store.js +6 -0
- package/dist/storage/json-store.js +2 -1
- package/dist/storage/kanban-store.js +6 -0
- package/dist/storage/workflow-store.js +386 -22
- package/dist/ws/agent-prompt.js +6 -2
- package/dist/ws/agent-runner.js +29 -18
- package/dist/ws/chat-handler.js +89 -0
- package/dist/ws/connection-manager.js +49 -2
- package/dist/ws/execution-channels.js +88 -0
- package/dist/ws/handler.js +32 -5
- package/dist/ws/message-parts.js +130 -12
- package/dist/ws/terminal-handler.js +26 -0
- package/package.json +12 -1
- package/public/avatars/user.jpg +0 -0
package/dist/routes/agent-sse.js
CHANGED
|
@@ -6,6 +6,7 @@ import * as workspaceService from '../services/workspace.js';
|
|
|
6
6
|
import { getThinkingRuntimeConfig } from '../services/llm-model-config.js';
|
|
7
7
|
import { buildAgentPrompt } from '../ws/agent-prompt.js';
|
|
8
8
|
import { wrapOnEventWithHooks } from '../services/hook-engine.js';
|
|
9
|
+
import { buildWorkflowEditorSystemPrompt, createWorkflowEditorFunctionTools } from '../services/builtin-tools/workflow-editor-tools.js';
|
|
9
10
|
const router = Router();
|
|
10
11
|
router.post('/run', async (req, res) => {
|
|
11
12
|
const body = req.body;
|
|
@@ -45,13 +46,24 @@ router.post('/run', async (req, res) => {
|
|
|
45
46
|
const requestedSkills = normalizeSkills(body.skills ?? body.skill) ?? preset.skills;
|
|
46
47
|
const configDir = agentService.getAgentConfigDir(workspaceId, { ...preset, skills: requestedSkills });
|
|
47
48
|
const skills = agentService.getAvailableSkillNames(configDir, requestedSkills);
|
|
49
|
+
const workflowAgent = normalizeWorkflowAgent(body.workflowAgent);
|
|
50
|
+
const functionTools = workflowAgent
|
|
51
|
+
? createWorkflowEditorFunctionTools({
|
|
52
|
+
workflow: workflowAgent.workflow,
|
|
53
|
+
nodeDefinitions: workflowAgent.nodeDefinitions,
|
|
54
|
+
})
|
|
55
|
+
: [];
|
|
56
|
+
const runtimeKind = workflowAgent ? 'langchain' : preset.runtimeKind;
|
|
57
|
+
const systemPrompt = workflowAgent
|
|
58
|
+
? buildWorkflowEditorSystemPrompt(workflowAgent.workflow, workflowAgent.selectedNodes)
|
|
59
|
+
: body.systemPrompt ?? preset.systemPrompt;
|
|
48
60
|
const output = [];
|
|
49
61
|
const workingDir = agentService.resolveWorkingDir(workspaceId, preset);
|
|
50
62
|
let completed = false;
|
|
51
63
|
prepareSse(res);
|
|
52
64
|
writeSse(res, 'session', { session, workspaceId });
|
|
53
65
|
const runtime = createAgentRuntime({
|
|
54
|
-
kind:
|
|
66
|
+
kind: runtimeKind,
|
|
55
67
|
provider: preset.modelProvider,
|
|
56
68
|
model: preset.modelId,
|
|
57
69
|
apiKey: preset.apiKey,
|
|
@@ -66,18 +78,19 @@ router.post('/run', async (req, res) => {
|
|
|
66
78
|
try {
|
|
67
79
|
agentService.updateStatus(workspaceId, session.id, 'active');
|
|
68
80
|
writeSse(res, 'status', { agentId: session.id, status: 'active' });
|
|
69
|
-
const result = await runtime.execute(buildAgentPrompt(workspaceId,
|
|
70
|
-
runtimeKind
|
|
81
|
+
const result = await runtime.execute(buildAgentPrompt(workspaceId, systemPrompt, userPrompt, normalizeMessages(body.messages), {
|
|
82
|
+
runtimeKind,
|
|
71
83
|
mcpServers: Object.keys(mcpServers ?? {}),
|
|
72
84
|
skills,
|
|
73
85
|
boundDirs: workspace.boundDirs,
|
|
74
86
|
workingDir,
|
|
75
|
-
excludeNativeClaudeMd:
|
|
76
|
-
builtInTools:
|
|
87
|
+
excludeNativeClaudeMd: runtimeKind === 'claude-code',
|
|
88
|
+
builtInTools: functionTools.map((tool) => ({ name: tool.name, description: tool.description })),
|
|
77
89
|
}), workingDir, {
|
|
78
90
|
maxTurns: normalizeMaxTurns(body.maxTurns),
|
|
79
91
|
mcpServers,
|
|
80
92
|
skills,
|
|
93
|
+
functionTools,
|
|
81
94
|
configDir,
|
|
82
95
|
sandboxDirs: preset.sandboxDirs,
|
|
83
96
|
userPrompt,
|
|
@@ -91,7 +104,7 @@ router.post('/run', async (req, res) => {
|
|
|
91
104
|
completed = true;
|
|
92
105
|
const displayOutput = output.length ? output : result.output;
|
|
93
106
|
agentService.complete(workspaceId, session.id, result.success ? undefined : result.error, {
|
|
94
|
-
runtime:
|
|
107
|
+
runtime: runtimeKind,
|
|
95
108
|
model: preset.modelId,
|
|
96
109
|
summary: result.summary,
|
|
97
110
|
output: displayOutput,
|
|
@@ -116,7 +129,7 @@ router.post('/run', async (req, res) => {
|
|
|
116
129
|
completed = true;
|
|
117
130
|
const error = err instanceof Error ? err.message : String(err);
|
|
118
131
|
agentService.complete(workspaceId, session.id, error, {
|
|
119
|
-
runtime:
|
|
132
|
+
runtime: runtimeKind,
|
|
120
133
|
model: preset.modelId,
|
|
121
134
|
summary: error,
|
|
122
135
|
output: output.length ? output : [error],
|
|
@@ -192,6 +205,56 @@ function normalizeSkills(input) {
|
|
|
192
205
|
function normalizeMaxTurns(value) {
|
|
193
206
|
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 100;
|
|
194
207
|
}
|
|
208
|
+
function normalizeWorkflowAgent(input) {
|
|
209
|
+
if (!input || typeof input !== 'object')
|
|
210
|
+
return null;
|
|
211
|
+
if (!isWorkflow(input.workflow))
|
|
212
|
+
return null;
|
|
213
|
+
const nodeDefinitions = Array.isArray(input.nodeDefinitions)
|
|
214
|
+
? input.nodeDefinitions.filter(isNodeDefinition)
|
|
215
|
+
: [];
|
|
216
|
+
if (!nodeDefinitions.length)
|
|
217
|
+
return null;
|
|
218
|
+
const selectedNodes = Array.isArray(input.selectedNodes)
|
|
219
|
+
? input.selectedNodes.filter(isWorkflowNode)
|
|
220
|
+
: undefined;
|
|
221
|
+
return { workflow: input.workflow, nodeDefinitions, selectedNodes };
|
|
222
|
+
}
|
|
223
|
+
function isWorkflow(value) {
|
|
224
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
225
|
+
return false;
|
|
226
|
+
const record = value;
|
|
227
|
+
return typeof record.id === 'string'
|
|
228
|
+
&& typeof record.name === 'string'
|
|
229
|
+
&& Array.isArray(record.nodes)
|
|
230
|
+
&& Array.isArray(record.edges)
|
|
231
|
+
&& record.nodes.every(isWorkflowNode);
|
|
232
|
+
}
|
|
233
|
+
function isWorkflowNode(value) {
|
|
234
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
235
|
+
return false;
|
|
236
|
+
const record = value;
|
|
237
|
+
const position = record.position;
|
|
238
|
+
return typeof record.id === 'string'
|
|
239
|
+
&& typeof record.type === 'string'
|
|
240
|
+
&& typeof record.label === 'string'
|
|
241
|
+
&& Boolean(position)
|
|
242
|
+
&& typeof position?.x === 'number'
|
|
243
|
+
&& typeof position?.y === 'number'
|
|
244
|
+
&& typeof record.data === 'object'
|
|
245
|
+
&& record.data !== null
|
|
246
|
+
&& !Array.isArray(record.data);
|
|
247
|
+
}
|
|
248
|
+
function isNodeDefinition(value) {
|
|
249
|
+
if (!value || typeof value !== 'object' || Array.isArray(value))
|
|
250
|
+
return false;
|
|
251
|
+
const record = value;
|
|
252
|
+
return typeof record.type === 'string'
|
|
253
|
+
&& typeof record.label === 'string'
|
|
254
|
+
&& typeof record.category === 'string'
|
|
255
|
+
&& typeof record.description === 'string'
|
|
256
|
+
&& Array.isArray(record.properties);
|
|
257
|
+
}
|
|
195
258
|
function serializeRuntimeEvent(event) {
|
|
196
259
|
if (event.type === 'tool_use') {
|
|
197
260
|
return {
|
package/dist/routes/channel.js
CHANGED
|
@@ -5,6 +5,8 @@ import { broadcastToWorkspace } from '../ws/connection-manager.js';
|
|
|
5
5
|
import { hasActiveChannelRuns, stopChannelRuns } from '../ws/agent-runner.js';
|
|
6
6
|
import { getToolDetail } from '../services/tool-detail.js';
|
|
7
7
|
import * as issueService from '../services/issue.js';
|
|
8
|
+
import { scheduleChannelTitleGeneration } from '../services/generated-title.js';
|
|
9
|
+
import { stripHtml } from '../ws/html-utils.js';
|
|
8
10
|
const router = Router({ mergeParams: true });
|
|
9
11
|
// GET /api/workspaces/:id/channels
|
|
10
12
|
router.get('/', (req, res) => {
|
|
@@ -12,10 +14,20 @@ router.get('/', (req, res) => {
|
|
|
12
14
|
});
|
|
13
15
|
// POST /api/workspaces/:id/channels
|
|
14
16
|
router.post('/', (req, res) => {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
+
const name = typeof req.body.name === 'string' ? req.body.name.trim() : '';
|
|
18
|
+
const titlePrompt = typeof req.body.titlePrompt === 'string' ? req.body.titlePrompt.trim() : '';
|
|
19
|
+
const { type, members } = req.body;
|
|
20
|
+
const channel = createChannel(req.params.id, { name, type: type || 'general', members });
|
|
17
21
|
broadcastToWorkspace(req.params.id, 'channel.updated', channel);
|
|
18
22
|
res.status(201).json(channel);
|
|
23
|
+
if (!name) {
|
|
24
|
+
scheduleChannelTitleGeneration({
|
|
25
|
+
workspaceId: req.params.id,
|
|
26
|
+
channelId: channel.id,
|
|
27
|
+
requirement: titlePrompt,
|
|
28
|
+
broadcast: (event, data) => broadcastToWorkspace(req.params.id, event, data),
|
|
29
|
+
});
|
|
30
|
+
}
|
|
19
31
|
});
|
|
20
32
|
// PUT /api/workspaces/:id/channels/:channelId
|
|
21
33
|
router.put('/:channelId', (req, res) => {
|
|
@@ -119,6 +131,15 @@ router.post('/:channelId/messages', (req, res) => {
|
|
|
119
131
|
}
|
|
120
132
|
const message = createMessage(id, channelId, { senderId: 'user', content, type, attachments });
|
|
121
133
|
broadcastToWorkspace(id, 'channel.message', message);
|
|
134
|
+
const channel = getChannel(id, channelId);
|
|
135
|
+
if (channel && !channel.name.trim()) {
|
|
136
|
+
scheduleChannelTitleGeneration({
|
|
137
|
+
workspaceId: id,
|
|
138
|
+
channelId: channelId,
|
|
139
|
+
requirement: stripHtml(content),
|
|
140
|
+
broadcast: (event, data) => broadcastToWorkspace(id, event, data),
|
|
141
|
+
});
|
|
142
|
+
}
|
|
122
143
|
res.status(201).json(message);
|
|
123
144
|
});
|
|
124
145
|
// PUT /api/workspaces/:id/channels/:channelId/messages/:messageId
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import * as chatService from '../services/chat.js';
|
|
3
|
+
import * as agentService from '../services/agent.js';
|
|
4
|
+
import { LangChainRuntime } from '../adapters/langchain-runtime.js';
|
|
5
|
+
import { createCommandFunctionTools, createDatabaseFunctionTools, createKanbanFunctionTools, createWorkflowExecutionFunctionTools, } from '../services/builtin-tools/index.js';
|
|
6
|
+
const router = Router();
|
|
7
|
+
router.post('/agents/:id/run', async (req, res) => {
|
|
8
|
+
const { id } = req.params;
|
|
9
|
+
const { content, regenerateFromMessageId } = req.body;
|
|
10
|
+
const regenerateContext = regenerateFromMessageId
|
|
11
|
+
? resolveRegenerateContext(id, regenerateFromMessageId)
|
|
12
|
+
: null;
|
|
13
|
+
const trimmedContent = (regenerateContext?.content ?? content)?.trim();
|
|
14
|
+
if (!trimmedContent) {
|
|
15
|
+
res.status(400).json({ error: 'content is required' });
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const agent = chatService.findAgent(id);
|
|
19
|
+
if (!agent) {
|
|
20
|
+
res.status(404).json({ error: 'Agent not found' });
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const baseURL = resolveChatAgentBaseURL(agent);
|
|
24
|
+
prepareSse(res);
|
|
25
|
+
if (!baseURL && requiresBaseURL(agent.provider)) {
|
|
26
|
+
writeSse(res, 'error', {
|
|
27
|
+
error: `${agent.provider} requires an API Base URL. Edit this chat agent and save the provider API address again.`,
|
|
28
|
+
});
|
|
29
|
+
res.end();
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (!regenerateContext) {
|
|
33
|
+
const userMsg = chatService.saveMessage({
|
|
34
|
+
agentId: id,
|
|
35
|
+
role: 'user',
|
|
36
|
+
content: trimmedContent,
|
|
37
|
+
});
|
|
38
|
+
writeSse(res, 'message_saved', userMsg);
|
|
39
|
+
}
|
|
40
|
+
const config = {
|
|
41
|
+
kind: 'langchain',
|
|
42
|
+
provider: agent.provider,
|
|
43
|
+
model: agent.model,
|
|
44
|
+
apiKey: agent.apiKey,
|
|
45
|
+
baseURL,
|
|
46
|
+
};
|
|
47
|
+
const runtime = new LangChainRuntime(config);
|
|
48
|
+
let completed = false;
|
|
49
|
+
res.on('close', () => {
|
|
50
|
+
if (!completed && !res.writableEnded)
|
|
51
|
+
runtime.stop();
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
const historyMessages = regenerateContext?.historyMessages ?? chatService.getRecentMessages(id, 20).slice(0, -1);
|
|
55
|
+
const historyPrompt = historyMessages
|
|
56
|
+
.filter(shouldIncludeHistoryMessage)
|
|
57
|
+
.map((message) => `${message.role === 'user' ? 'User' : 'Assistant'}: ${message.content}`)
|
|
58
|
+
.join('\n\n');
|
|
59
|
+
const prompt = historyPrompt
|
|
60
|
+
? `${historyPrompt}\n\nUser: ${trimmedContent}\nAssistant:`
|
|
61
|
+
: trimmedContent;
|
|
62
|
+
const workingDir = chatService.getAgentWorkingDir(id) || process.cwd();
|
|
63
|
+
const configDir = chatService.getAgentConfigDir(id) || undefined;
|
|
64
|
+
const tools = normalizeToolNames(agent.tools);
|
|
65
|
+
const functionTools = [
|
|
66
|
+
...createCommandFunctionTools(id, tools),
|
|
67
|
+
...createDatabaseFunctionTools(id, tools),
|
|
68
|
+
...createKanbanFunctionTools(id, tools),
|
|
69
|
+
...createWorkflowExecutionFunctionTools(tools),
|
|
70
|
+
];
|
|
71
|
+
const result = await runtime.execute(prompt, workingDir, {
|
|
72
|
+
maxTurns: 100,
|
|
73
|
+
functionTools,
|
|
74
|
+
mcpServers: agentService.getMcpServers(agent.mcps),
|
|
75
|
+
skills: normalizeSkillNames(agent.skills),
|
|
76
|
+
configDir,
|
|
77
|
+
systemPrompt: agent.systemPrompt,
|
|
78
|
+
outputStyle: agent.outputStyle,
|
|
79
|
+
onEvent: (event) => {
|
|
80
|
+
writeRuntimeEvent(res, event);
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
if (!result.success) {
|
|
84
|
+
completed = true;
|
|
85
|
+
writeSse(res, 'error', { error: result.error ?? result.summary });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const agentContent = result.output
|
|
89
|
+
.filter((line) => !line.startsWith('Tool:') && !line.startsWith('[Usage]'))
|
|
90
|
+
.join('\n')
|
|
91
|
+
|| result.summary;
|
|
92
|
+
const agentMsg = chatService.saveMessage({
|
|
93
|
+
agentId: id,
|
|
94
|
+
role: 'agent',
|
|
95
|
+
content: agentContent,
|
|
96
|
+
usage: result.usage,
|
|
97
|
+
});
|
|
98
|
+
completed = true;
|
|
99
|
+
writeSse(res, 'completed', { message: agentMsg, success: result.success, error: result.error });
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
completed = true;
|
|
103
|
+
writeSse(res, 'error', { error: err instanceof Error ? err.message : String(err) });
|
|
104
|
+
}
|
|
105
|
+
finally {
|
|
106
|
+
res.end();
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
function prepareSse(res) {
|
|
110
|
+
res.status(200);
|
|
111
|
+
res.setHeader('Content-Type', 'text/event-stream; charset=utf-8');
|
|
112
|
+
res.setHeader('Cache-Control', 'no-cache, no-transform');
|
|
113
|
+
res.setHeader('Connection', 'keep-alive');
|
|
114
|
+
res.setHeader('X-Accel-Buffering', 'no');
|
|
115
|
+
res.flushHeaders?.();
|
|
116
|
+
res.socket?.setNoDelay?.(true);
|
|
117
|
+
}
|
|
118
|
+
function writeSse(res, event, data) {
|
|
119
|
+
res.write(`event: ${event}\n`);
|
|
120
|
+
res.write(`data: ${JSON.stringify(data)}\n\n`);
|
|
121
|
+
const flushable = res;
|
|
122
|
+
if (typeof flushable.flush === 'function')
|
|
123
|
+
flushable.flush();
|
|
124
|
+
}
|
|
125
|
+
function writeRuntimeEvent(res, event) {
|
|
126
|
+
switch (event.type) {
|
|
127
|
+
case 'output':
|
|
128
|
+
if (!event.line.startsWith('[Usage]'))
|
|
129
|
+
writeSse(res, 'output', { chunk: event.line });
|
|
130
|
+
break;
|
|
131
|
+
case 'reasoning':
|
|
132
|
+
writeSse(res, 'thinking', { chunk: event.text, status: event.status });
|
|
133
|
+
break;
|
|
134
|
+
case 'tool_use':
|
|
135
|
+
writeSse(res, 'tool_use', { name: event.name, input: event.input });
|
|
136
|
+
break;
|
|
137
|
+
case 'tool_result':
|
|
138
|
+
writeSse(res, 'tool_result', { name: event.toolUseId, result: event.result });
|
|
139
|
+
break;
|
|
140
|
+
case 'session':
|
|
141
|
+
case 'hook_event':
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function shouldIncludeHistoryMessage(message) {
|
|
146
|
+
return !(message.role === 'agent' && message.content.trim() === 'LangChain execution failed');
|
|
147
|
+
}
|
|
148
|
+
function resolveRegenerateContext(agentId, messageId) {
|
|
149
|
+
const messages = chatService.listMessages(agentId);
|
|
150
|
+
const targetIndex = messages.findIndex((message) => message.id === messageId && message.role === 'agent');
|
|
151
|
+
if (targetIndex === -1)
|
|
152
|
+
return null;
|
|
153
|
+
for (let index = targetIndex - 1; index >= 0; index -= 1) {
|
|
154
|
+
const message = messages[index];
|
|
155
|
+
if (message?.role === 'user') {
|
|
156
|
+
return {
|
|
157
|
+
content: message.content,
|
|
158
|
+
historyMessages: messages.slice(0, index),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
function resolveChatAgentBaseURL(agent) {
|
|
165
|
+
return agent.baseURL?.trim() || agent.apiBase?.trim() || undefined;
|
|
166
|
+
}
|
|
167
|
+
function requiresBaseURL(provider) {
|
|
168
|
+
return provider === 'openai-chat-completions'
|
|
169
|
+
|| provider === 'openai-responses'
|
|
170
|
+
|| provider === 'anthropic-messages'
|
|
171
|
+
|| provider === 'gemini-generate-content';
|
|
172
|
+
}
|
|
173
|
+
function normalizeSkillNames(skills) {
|
|
174
|
+
if (!Array.isArray(skills))
|
|
175
|
+
return [];
|
|
176
|
+
return skills
|
|
177
|
+
.map((skill) => typeof skill === 'string'
|
|
178
|
+
? skill
|
|
179
|
+
: skill && typeof skill === 'object' && 'name' in skill && typeof skill.name === 'string'
|
|
180
|
+
? skill.name
|
|
181
|
+
: '')
|
|
182
|
+
.map((skill) => skill.trim())
|
|
183
|
+
.filter(Boolean);
|
|
184
|
+
}
|
|
185
|
+
function normalizeToolNames(tools) {
|
|
186
|
+
if (!Array.isArray(tools))
|
|
187
|
+
return undefined;
|
|
188
|
+
return tools.filter((tool) => typeof tool === 'string');
|
|
189
|
+
}
|
|
190
|
+
export default router;
|
|
191
|
+
//# sourceMappingURL=chat-run.js.map
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Router } from 'express';
|
|
2
|
+
import * as svc from '../services/chat.js';
|
|
3
|
+
import * as fileService from '../services/file.js';
|
|
4
|
+
const router = Router();
|
|
5
|
+
// GET /api/chat/agents — list all chat agents
|
|
6
|
+
router.get('/agents', (_req, res) => {
|
|
7
|
+
try {
|
|
8
|
+
res.json(svc.listAgents());
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
res.status(500).json({ error: error.message });
|
|
12
|
+
}
|
|
13
|
+
});
|
|
14
|
+
// POST /api/chat/agents — create agent (requires name, provider, model, apiKey)
|
|
15
|
+
router.post('/agents', (req, res) => {
|
|
16
|
+
const body = req.body;
|
|
17
|
+
const name = typeof body.name === 'string' ? body.name.trim() : '';
|
|
18
|
+
const provider = stringValue(body.provider) || stringValue(body.modelProvider);
|
|
19
|
+
const model = stringValue(body.model) || stringValue(body.modelId);
|
|
20
|
+
if (!name || !provider || !model) {
|
|
21
|
+
res.status(400).json({ error: 'name, provider, and model are required' });
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const agent = svc.createAgent(body);
|
|
26
|
+
res.status(201).json(agent);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
res.status(500).json({ error: error.message });
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
// PUT /api/chat/agents/:id — update agent
|
|
33
|
+
router.put('/agents/:id', (req, res) => {
|
|
34
|
+
const { id } = req.params;
|
|
35
|
+
if (!id) {
|
|
36
|
+
res.status(400).json({ error: 'id is required' });
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const patch = normalizeChatAgentPatch(req.body);
|
|
41
|
+
const agent = svc.updateAgent(id, patch);
|
|
42
|
+
if (!agent) {
|
|
43
|
+
res.status(404).json({ error: 'Agent not found' });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
res.json(agent);
|
|
47
|
+
}
|
|
48
|
+
catch (error) {
|
|
49
|
+
res.status(500).json({ error: error.message });
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
function normalizeChatAgentPatch(body) {
|
|
53
|
+
if (!body || typeof body !== 'object' || Array.isArray(body))
|
|
54
|
+
return {};
|
|
55
|
+
const patch = { ...body };
|
|
56
|
+
if (!patch.baseURL && patch.apiBase) {
|
|
57
|
+
patch.baseURL = patch.apiBase;
|
|
58
|
+
}
|
|
59
|
+
delete patch.apiBase;
|
|
60
|
+
return patch;
|
|
61
|
+
}
|
|
62
|
+
// DELETE /api/chat/agents/:id — delete agent + its messages
|
|
63
|
+
router.delete('/agents/:id', (req, res) => {
|
|
64
|
+
const { id } = req.params;
|
|
65
|
+
if (!id) {
|
|
66
|
+
res.status(400).json({ error: 'id is required' });
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const deleted = svc.deleteAgent(id);
|
|
71
|
+
if (!deleted) {
|
|
72
|
+
res.status(404).json({ error: 'Agent not found' });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
res.status(204).send();
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
res.status(500).json({ error: error.message });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
// GET /api/chat/agents/:id/messages — list messages (?limit=50&before=msgId)
|
|
82
|
+
router.get('/agents/:id/messages', (req, res) => {
|
|
83
|
+
const { id } = req.params;
|
|
84
|
+
if (!id) {
|
|
85
|
+
res.status(400).json({ error: 'id is required' });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const agent = svc.findAgent(id);
|
|
90
|
+
if (!agent) {
|
|
91
|
+
res.status(404).json({ error: 'Agent not found' });
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const limit = req.query.limit ? parseInt(req.query.limit, 10) : undefined;
|
|
95
|
+
const before = req.query.before;
|
|
96
|
+
res.json(svc.listMessages(id, limit, before));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
res.status(500).json({ error: error.message });
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
// DELETE /api/chat/agents/:id/messages — clear messages
|
|
103
|
+
router.delete('/agents/:id/messages', (req, res) => {
|
|
104
|
+
const { id } = req.params;
|
|
105
|
+
if (!id) {
|
|
106
|
+
res.status(400).json({ error: 'id is required' });
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
svc.clearMessages(id);
|
|
111
|
+
res.status(204).send();
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
res.status(500).json({ error: error.message });
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
// GET /api/chat/agents/:id/workspace/tree - read chat agent working directory
|
|
118
|
+
router.get('/agents/:id/workspace/tree', async (req, res) => {
|
|
119
|
+
const { id } = req.params;
|
|
120
|
+
if (!id) {
|
|
121
|
+
res.status(400).json({ error: 'id is required' });
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
try {
|
|
125
|
+
const workspace = svc.getAgentWorkspace(id);
|
|
126
|
+
if (!workspace) {
|
|
127
|
+
res.status(404).json({ error: 'Agent workspace not found' });
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const relPath = typeof req.query.path === 'string' ? req.query.path : '';
|
|
131
|
+
const depth = req.query.depth ? Number(req.query.depth) : Infinity;
|
|
132
|
+
res.json(await fileService.readTree(workspace, relPath, depth));
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
res.status(500).json({ error: error.message });
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
function stringValue(value) {
|
|
139
|
+
return typeof value === 'string' ? value.trim() : '';
|
|
140
|
+
}
|
|
141
|
+
export default router;
|
|
142
|
+
//# sourceMappingURL=chat.js.map
|
package/dist/routes/command.js
CHANGED
|
@@ -104,5 +104,21 @@ router.post('/:commandId/stop', (req, res) => {
|
|
|
104
104
|
res.status(400).json({ error: error.message });
|
|
105
105
|
}
|
|
106
106
|
});
|
|
107
|
+
router.post('/:commandId/restart', (req, res) => {
|
|
108
|
+
const { id: workspaceId, commandId } = req.params;
|
|
109
|
+
if (!workspaceId || !commandId) {
|
|
110
|
+
res.status(400).json({ error: 'workspaceId and commandId required' });
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const sessionId = processManager.restartCommand(workspaceId, commandId);
|
|
115
|
+
console.log(`[command] POST restart: workspace=${workspaceId} command=${commandId} -> session=${sessionId}`);
|
|
116
|
+
res.json({ sessionId });
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error(`[command] POST restart error: workspace=${workspaceId} command=${commandId}`, error.message);
|
|
120
|
+
res.status(400).json({ error: error.message });
|
|
121
|
+
}
|
|
122
|
+
});
|
|
107
123
|
export default router;
|
|
108
124
|
//# sourceMappingURL=command.js.map
|