@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/ws/agent-runner.js
CHANGED
|
@@ -4,7 +4,7 @@ import { broadcastToWorkspace } from './connection-manager.js';
|
|
|
4
4
|
import { createMessage, updateMessage, listMessages } from '../services/message.js';
|
|
5
5
|
import { getChannel, updateChannel } from '../services/channel.js';
|
|
6
6
|
import * as issueService from '../services/issue.js';
|
|
7
|
-
import { createIssueFunctionTools, createCommandFunctionTools, createDatabaseFunctionTools, createKanbanFunctionTools } from '../services/builtin-tools/index.js';
|
|
7
|
+
import { createIssueFunctionTools, createCommandFunctionTools, createDatabaseFunctionTools, createKanbanFunctionTools, createWorkflowExecutionFunctionTools } from '../services/builtin-tools/index.js';
|
|
8
8
|
import { startScheduler } from '../agents/scheduler-agent.js';
|
|
9
9
|
import * as agentService from '../services/agent.js';
|
|
10
10
|
import * as wsService from '../services/workspace.js';
|
|
@@ -57,17 +57,17 @@ export function stopChannelRuns(workspaceId, channelId) {
|
|
|
57
57
|
agentId: run.agentId,
|
|
58
58
|
error: 'Stopped by user',
|
|
59
59
|
});
|
|
60
|
+
const existing = listMessages(workspaceId, channelId).find((m) => m.id === run.messageId);
|
|
61
|
+
const preservedParts = (existing?.parts ?? []).map((part) => 'status' in part && part.status === 'streaming' ? { ...part, status: 'completed' } : part);
|
|
62
|
+
const stoppedPart = {
|
|
63
|
+
id: `error-stopped-${run.agentId}`,
|
|
64
|
+
type: 'error',
|
|
65
|
+
title: 'Stopped by user',
|
|
66
|
+
message: 'The agent run was stopped by user.',
|
|
67
|
+
};
|
|
60
68
|
const message = updateMessage(workspaceId, channelId, run.messageId, {
|
|
61
|
-
content: 'Stopped by user',
|
|
62
69
|
status: 'error',
|
|
63
|
-
parts: [
|
|
64
|
-
{
|
|
65
|
-
id: `terminal-stopped-${run.agentId}`,
|
|
66
|
-
type: 'terminal',
|
|
67
|
-
output: 'Stopped by user',
|
|
68
|
-
status: 'error',
|
|
69
|
-
},
|
|
70
|
-
],
|
|
70
|
+
parts: [...preservedParts, stoppedPart],
|
|
71
71
|
});
|
|
72
72
|
if (message)
|
|
73
73
|
broadcastToWorkspace(workspaceId, 'channel.message.updated', message);
|
|
@@ -170,6 +170,7 @@ export async function runMentionedAgent(workspaceId, channelId, agentConfigId, p
|
|
|
170
170
|
...createCommandFunctionTools(workspaceId, preset.tools),
|
|
171
171
|
...createDatabaseFunctionTools(workspaceId, preset.tools),
|
|
172
172
|
...createKanbanFunctionTools(workspaceId, preset.tools),
|
|
173
|
+
...createWorkflowExecutionFunctionTools(preset.tools),
|
|
173
174
|
];
|
|
174
175
|
const workingDir = agentService.resolveWorkingDir(workspaceId, preset);
|
|
175
176
|
const startTime = Date.now();
|
|
@@ -223,7 +224,10 @@ export async function runMentionedAgent(workspaceId, channelId, agentConfigId, p
|
|
|
223
224
|
const liveReasoning = [];
|
|
224
225
|
let agentPrompt = '';
|
|
225
226
|
let runtimeUserPrompt = prompt;
|
|
226
|
-
|
|
227
|
+
const contextLength = normalizeContextLength(options.contextLength);
|
|
228
|
+
let runtimeSessionId = contextLength === 0
|
|
229
|
+
? undefined
|
|
230
|
+
: options.resumeSessionId ?? existingMessage?.metadata?.runtimeSessionId;
|
|
227
231
|
const persistentContext = buildPersistentAgentContextDetails({
|
|
228
232
|
workspaceId,
|
|
229
233
|
workingDir: workspace?.boundDirs?.[0] || workingDir,
|
|
@@ -247,13 +251,15 @@ export async function runMentionedAgent(workspaceId, channelId, agentConfigId, p
|
|
|
247
251
|
runtime,
|
|
248
252
|
};
|
|
249
253
|
trackChannelRun(workspaceId, channelId, activeRun);
|
|
250
|
-
const history =
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
254
|
+
const history = contextLength === 0
|
|
255
|
+
? []
|
|
256
|
+
: filterPromptHistory(listMessages(workspaceId, channelId, { limit: contextLength }), {
|
|
257
|
+
excludeMessageIds: [
|
|
258
|
+
...(!existingMessage ? [pending.id] : []),
|
|
259
|
+
...(options.excludeHistoryMessageIds ?? []),
|
|
260
|
+
],
|
|
261
|
+
excludeReplyIds: options.excludeHistoryReplyIds,
|
|
262
|
+
});
|
|
257
263
|
const isRuntimeSessionResume = Boolean(options.resumeSessionId && (preset.runtimeKind === 'claude-code' || preset.runtimeKind === 'codex'));
|
|
258
264
|
runtimeUserPrompt = expandAgentSlashCommandPrompt(prompt, configDir);
|
|
259
265
|
agentPrompt = isRuntimeSessionResume
|
|
@@ -851,4 +857,9 @@ function estimateTextTokens(text) {
|
|
|
851
857
|
const words = nonCjkText.match(/[A-Za-z0-9_]+|[^\sA-Za-z0-9_]/g)?.length ?? 0;
|
|
852
858
|
return Math.max(1, Math.ceil(cjkChars + words * 0.75));
|
|
853
859
|
}
|
|
860
|
+
function normalizeContextLength(value) {
|
|
861
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
862
|
+
return 20;
|
|
863
|
+
return Math.min(20, Math.max(0, Math.trunc(value)));
|
|
864
|
+
}
|
|
854
865
|
//# sourceMappingURL=agent-runner.js.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { broadcastToWorkspace } from './connection-manager.js';
|
|
2
|
+
import { registerHandler } from './handler.js';
|
|
3
|
+
import * as chatService from '../services/chat.js';
|
|
4
|
+
import { LangChainRuntime } from '../adapters/langchain-runtime.js';
|
|
5
|
+
const activeRuns = new Map();
|
|
6
|
+
export function registerChatHandlers() {
|
|
7
|
+
// chat.message: { agentId, content }
|
|
8
|
+
registerHandler('chat.message', (_ws, workspaceId, data) => {
|
|
9
|
+
const { agentId, content } = data;
|
|
10
|
+
if (!agentId || !content)
|
|
11
|
+
return;
|
|
12
|
+
const agent = chatService.findAgent(agentId);
|
|
13
|
+
if (!agent) {
|
|
14
|
+
broadcastToWorkspace(workspaceId, 'chat.agent.error', { agentId, error: 'Agent not found' });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
// Save user message
|
|
18
|
+
const userMsg = chatService.saveMessage({ agentId, role: 'user', content });
|
|
19
|
+
broadcastToWorkspace(workspaceId, 'chat.message.saved', userMsg);
|
|
20
|
+
// Build runtime config (fixed langchain)
|
|
21
|
+
const config = {
|
|
22
|
+
kind: 'langchain',
|
|
23
|
+
provider: agent.provider,
|
|
24
|
+
model: agent.model,
|
|
25
|
+
apiKey: agent.apiKey,
|
|
26
|
+
baseURL: agent.baseURL,
|
|
27
|
+
};
|
|
28
|
+
const runtime = new LangChainRuntime(config);
|
|
29
|
+
activeRuns.set(agentId, runtime);
|
|
30
|
+
// Build conversation history from recent messages
|
|
31
|
+
const recent = chatService.getRecentMessages(agentId, 20);
|
|
32
|
+
const historyPrompt = recent
|
|
33
|
+
.slice(0, -1) // exclude the just-saved user message (already in recent)
|
|
34
|
+
.map((m) => `${m.role === 'user' ? 'User' : 'Assistant'}: ${m.content}`)
|
|
35
|
+
.join('\n\n');
|
|
36
|
+
const prompt = historyPrompt ? `${historyPrompt}\n\nUser: ${content}\nAssistant:` : content;
|
|
37
|
+
// Execute with streaming events
|
|
38
|
+
runtime.execute(prompt, process.cwd(), {
|
|
39
|
+
systemPrompt: agent.systemPrompt,
|
|
40
|
+
onEvent: (event) => {
|
|
41
|
+
switch (event.type) {
|
|
42
|
+
case 'output':
|
|
43
|
+
broadcastToWorkspace(workspaceId, 'chat.agent.output', { agentId, chunk: event.line });
|
|
44
|
+
break;
|
|
45
|
+
case 'reasoning':
|
|
46
|
+
broadcastToWorkspace(workspaceId, 'chat.agent.thinking', { agentId, chunk: event.text });
|
|
47
|
+
break;
|
|
48
|
+
case 'tool_use':
|
|
49
|
+
broadcastToWorkspace(workspaceId, 'chat.agent.tool_use', { agentId, name: event.name, input: event.input });
|
|
50
|
+
break;
|
|
51
|
+
case 'tool_result':
|
|
52
|
+
broadcastToWorkspace(workspaceId, 'chat.agent.tool_result', { agentId, name: event.toolUseId, result: event.result });
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
})
|
|
57
|
+
.then((result) => {
|
|
58
|
+
const agentContent = result.output.filter((line) => !line.startsWith('Tool:')).join('\n') || result.summary;
|
|
59
|
+
const agentMsg = chatService.saveMessage({
|
|
60
|
+
agentId,
|
|
61
|
+
role: 'agent',
|
|
62
|
+
content: agentContent,
|
|
63
|
+
usage: result.usage,
|
|
64
|
+
});
|
|
65
|
+
broadcastToWorkspace(workspaceId, 'chat.agent.completed', { agentId, message: agentMsg });
|
|
66
|
+
})
|
|
67
|
+
.catch((err) => {
|
|
68
|
+
broadcastToWorkspace(workspaceId, 'chat.agent.error', {
|
|
69
|
+
agentId,
|
|
70
|
+
error: err instanceof Error ? err.message : String(err),
|
|
71
|
+
});
|
|
72
|
+
})
|
|
73
|
+
.finally(() => {
|
|
74
|
+
activeRuns.delete(agentId);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
// chat.stop: { agentId }
|
|
78
|
+
registerHandler('chat.stop', (_ws, _workspaceId, data) => {
|
|
79
|
+
const { agentId } = data;
|
|
80
|
+
if (!agentId)
|
|
81
|
+
return;
|
|
82
|
+
const runtime = activeRuns.get(agentId);
|
|
83
|
+
if (runtime) {
|
|
84
|
+
runtime.stop();
|
|
85
|
+
activeRuns.delete(agentId);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=chat-handler.js.map
|
|
@@ -1,12 +1,59 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
1
2
|
import { publishWorkspaceEvent } from '../services/notification-hub/index.js';
|
|
2
3
|
const connections = new Map();
|
|
4
|
+
const clientIdIndex = new Map();
|
|
5
|
+
const clientConnectedCallbacks = [];
|
|
6
|
+
const clientDisconnectedCallbacks = [];
|
|
7
|
+
let interactionResponseHandler = null;
|
|
3
8
|
export function addConnection(ws, workspaceId) {
|
|
4
|
-
|
|
5
|
-
ws
|
|
9
|
+
const clientId = randomUUID();
|
|
10
|
+
const conn = { ws, workspaceId, clientId };
|
|
11
|
+
connections.set(ws, conn);
|
|
12
|
+
clientIdIndex.set(clientId, conn);
|
|
13
|
+
ws.on('close', () => {
|
|
14
|
+
connections.delete(ws);
|
|
15
|
+
clientIdIndex.delete(clientId);
|
|
16
|
+
for (const cb of clientDisconnectedCallbacks)
|
|
17
|
+
cb(clientId);
|
|
18
|
+
});
|
|
19
|
+
for (const cb of clientConnectedCallbacks)
|
|
20
|
+
cb(clientId, ws);
|
|
21
|
+
return clientId;
|
|
6
22
|
}
|
|
7
23
|
export function removeConnection(ws) {
|
|
24
|
+
const conn = connections.get(ws);
|
|
25
|
+
if (conn) {
|
|
26
|
+
clientIdIndex.delete(conn.clientId);
|
|
27
|
+
for (const cb of clientDisconnectedCallbacks)
|
|
28
|
+
cb(conn.clientId);
|
|
29
|
+
}
|
|
8
30
|
connections.delete(ws);
|
|
9
31
|
}
|
|
32
|
+
export function getClientId(ws) {
|
|
33
|
+
return connections.get(ws)?.clientId;
|
|
34
|
+
}
|
|
35
|
+
export function getConnectionByClientId(clientId) {
|
|
36
|
+
return clientIdIndex.get(clientId);
|
|
37
|
+
}
|
|
38
|
+
export function sendToClient(clientId, data) {
|
|
39
|
+
const conn = clientIdIndex.get(clientId);
|
|
40
|
+
if (!conn || conn.ws.readyState !== 1)
|
|
41
|
+
return false;
|
|
42
|
+
conn.ws.send(JSON.stringify(data));
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
export function onClientConnected(cb) {
|
|
46
|
+
clientConnectedCallbacks.push(cb);
|
|
47
|
+
}
|
|
48
|
+
export function onClientDisconnected(cb) {
|
|
49
|
+
clientDisconnectedCallbacks.push(cb);
|
|
50
|
+
}
|
|
51
|
+
export function setInteractionResponseHandler(handler) {
|
|
52
|
+
interactionResponseHandler = handler;
|
|
53
|
+
}
|
|
54
|
+
export function handleInteractionResponse(response, clientId) {
|
|
55
|
+
interactionResponseHandler?.(response, clientId);
|
|
56
|
+
}
|
|
10
57
|
export function getConnectionsByWorkspace(workspaceId) {
|
|
11
58
|
const result = [];
|
|
12
59
|
for (const [ws, conn] of connections) {
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// WS execution channel handlers — register workflow execution control events
|
|
2
|
+
import { getClientId } from './connection-manager.js';
|
|
3
|
+
import { registerHandler } from './handler.js';
|
|
4
|
+
export function registerExecutionChannels(executionManager) {
|
|
5
|
+
registerHandler('workflow:execute', async (ws, _workspaceId, data) => {
|
|
6
|
+
const clientId = getClientId(ws);
|
|
7
|
+
if (!clientId)
|
|
8
|
+
return;
|
|
9
|
+
try {
|
|
10
|
+
const result = await executionManager.execute(data, clientId, (channel, payload) => {
|
|
11
|
+
if (ws.readyState === 1) {
|
|
12
|
+
ws.send(JSON.stringify({ event: channel, data: payload }));
|
|
13
|
+
}
|
|
14
|
+
});
|
|
15
|
+
ws.send(JSON.stringify({
|
|
16
|
+
event: 'workflow:execute:result',
|
|
17
|
+
data: result,
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
20
|
+
catch (err) {
|
|
21
|
+
ws.send(JSON.stringify({
|
|
22
|
+
event: 'workflow:execute:error',
|
|
23
|
+
data: { error: err.message || String(err) },
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
registerHandler('workflow:pause', (ws, _workspaceId, data) => {
|
|
28
|
+
const { executionId } = data;
|
|
29
|
+
if (!executionId)
|
|
30
|
+
return;
|
|
31
|
+
try {
|
|
32
|
+
const result = executionManager.pause(executionId);
|
|
33
|
+
ws.send(JSON.stringify({ event: 'workflow:pause:result', data: result }));
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
ws.send(JSON.stringify({ event: 'workflow:pause:error', data: { error: err.message } }));
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
registerHandler('workflow:resume', async (ws, _workspaceId, data) => {
|
|
40
|
+
const { executionId } = data;
|
|
41
|
+
if (!executionId)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
const result = await executionManager.resume(executionId);
|
|
45
|
+
ws.send(JSON.stringify({ event: 'workflow:resume:result', data: result }));
|
|
46
|
+
}
|
|
47
|
+
catch (err) {
|
|
48
|
+
ws.send(JSON.stringify({ event: 'workflow:resume:error', data: { error: err.message } }));
|
|
49
|
+
}
|
|
50
|
+
});
|
|
51
|
+
registerHandler('workflow:stop', (ws, _workspaceId, data) => {
|
|
52
|
+
const { executionId } = data;
|
|
53
|
+
if (!executionId)
|
|
54
|
+
return;
|
|
55
|
+
try {
|
|
56
|
+
const result = executionManager.stop(executionId);
|
|
57
|
+
ws.send(JSON.stringify({ event: 'workflow:stop:result', data: result }));
|
|
58
|
+
}
|
|
59
|
+
catch (err) {
|
|
60
|
+
ws.send(JSON.stringify({ event: 'workflow:stop:error', data: { error: err.message } }));
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
registerHandler('workflow:debug-node', async (ws, _workspaceId, data) => {
|
|
64
|
+
const clientId = getClientId(ws);
|
|
65
|
+
if (!clientId)
|
|
66
|
+
return;
|
|
67
|
+
try {
|
|
68
|
+
const result = await executionManager.debugNode(data, clientId);
|
|
69
|
+
ws.send(JSON.stringify({ event: 'workflow:debug-node:result', data: result }));
|
|
70
|
+
}
|
|
71
|
+
catch (err) {
|
|
72
|
+
ws.send(JSON.stringify({ event: 'workflow:debug-node:error', data: { error: err.message } }));
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
registerHandler('workflow:get-execution-recovery', (ws, _workspaceId, data) => {
|
|
76
|
+
const clientId = getClientId(ws);
|
|
77
|
+
if (!clientId)
|
|
78
|
+
return;
|
|
79
|
+
try {
|
|
80
|
+
const result = executionManager.getExecutionRecovery(data, clientId);
|
|
81
|
+
ws.send(JSON.stringify({ event: 'workflow:get-execution-recovery:result', data: result }));
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
ws.send(JSON.stringify({ event: 'workflow:get-execution-recovery:error', data: { error: err.message } }));
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
//# sourceMappingURL=execution-channels.js.map
|
package/dist/ws/handler.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
import { addConnection, broadcastToWorkspace } from './connection-manager.js';
|
|
1
|
+
import { addConnection, broadcastToWorkspace, getClientId, handleInteractionResponse } from './connection-manager.js';
|
|
2
2
|
import { handleTerminalEvent, sendTerminalSessions } from './terminal-handler.js';
|
|
3
|
+
import { registerChatHandlers } from './chat-handler.js';
|
|
3
4
|
import { appendMessageReply, createMessage } from '../services/message.js';
|
|
4
5
|
import { getChannel } from '../services/channel.js';
|
|
6
|
+
import { scheduleChannelTitleGeneration } from '../services/generated-title.js';
|
|
5
7
|
import * as agentService from '../services/agent.js';
|
|
6
8
|
import { runMentionedAgent, stopChannelRuns, handleAnswerQuestion, ensureScheduler, makeContext } from './agent-runner.js';
|
|
7
9
|
import { stripHtml, extractMentionIds } from './html-utils.js';
|
|
@@ -10,7 +12,7 @@ export function registerHandler(event, handler) {
|
|
|
10
12
|
handlers.set(event, handler);
|
|
11
13
|
}
|
|
12
14
|
export function handleConnection(ws, workspaceId) {
|
|
13
|
-
addConnection(ws, workspaceId);
|
|
15
|
+
const clientId = addConnection(ws, workspaceId);
|
|
14
16
|
const ctx = makeContext(workspaceId);
|
|
15
17
|
ensureScheduler(workspaceId, ctx);
|
|
16
18
|
ws.send(JSON.stringify({
|
|
@@ -52,10 +54,12 @@ for (const evt of terminalEvents) {
|
|
|
52
54
|
}
|
|
53
55
|
// Register channel handlers
|
|
54
56
|
registerHandler('channel.message', (_ws, workspaceId, data) => {
|
|
55
|
-
const { channelId, content, type, mentions, attachments, replyToMessageId } = data;
|
|
57
|
+
const { channelId, content, type, mentions, attachments, replyToMessageId, contextLength } = data;
|
|
58
|
+
const normalizedContextLength = normalizeContextLength(contextLength);
|
|
56
59
|
if (!channelId || (!content && !attachments?.length))
|
|
57
60
|
return;
|
|
58
|
-
|
|
61
|
+
const channel = getChannel(workspaceId, channelId);
|
|
62
|
+
if (!channel)
|
|
59
63
|
return;
|
|
60
64
|
if (replyToMessageId) {
|
|
61
65
|
const updated = appendMessageReply(workspaceId, channelId, replyToMessageId, {
|
|
@@ -72,8 +76,9 @@ registerHandler('channel.message', (_ws, workspaceId, data) => {
|
|
|
72
76
|
void runMentionedAgent(workspaceId, channelId, agentId, stripHtml(content), {
|
|
73
77
|
messageId: updated.id,
|
|
74
78
|
appendUserMessage: stripHtml(content),
|
|
75
|
-
resumeSessionId: updated.metadata?.runtimeSessionId,
|
|
79
|
+
resumeSessionId: normalizedContextLength > 0 ? updated.metadata?.runtimeSessionId : undefined,
|
|
76
80
|
excludeHistoryReplyIds: latestReplyId ? [latestReplyId] : undefined,
|
|
81
|
+
contextLength: normalizedContextLength,
|
|
77
82
|
});
|
|
78
83
|
}
|
|
79
84
|
return;
|
|
@@ -85,13 +90,27 @@ registerHandler('channel.message', (_ws, workspaceId, data) => {
|
|
|
85
90
|
attachments,
|
|
86
91
|
});
|
|
87
92
|
broadcastToWorkspace(workspaceId, 'channel.message', message);
|
|
93
|
+
if (!channel.name.trim()) {
|
|
94
|
+
scheduleChannelTitleGeneration({
|
|
95
|
+
workspaceId,
|
|
96
|
+
channelId,
|
|
97
|
+
requirement: stripHtml(content),
|
|
98
|
+
broadcast: (event, payload) => broadcastToWorkspace(workspaceId, event, payload),
|
|
99
|
+
});
|
|
100
|
+
}
|
|
88
101
|
const agentIds = [...new Set([...(mentions || []), ...extractMentionIds(content)].filter(Boolean))];
|
|
89
102
|
for (const agentId of agentIds) {
|
|
90
103
|
void runMentionedAgent(workspaceId, channelId, agentId, stripHtml(content), {
|
|
91
104
|
excludeHistoryMessageIds: [message.id],
|
|
105
|
+
contextLength: normalizedContextLength,
|
|
92
106
|
});
|
|
93
107
|
}
|
|
94
108
|
});
|
|
109
|
+
function normalizeContextLength(value) {
|
|
110
|
+
if (typeof value !== 'number' || !Number.isFinite(value))
|
|
111
|
+
return 20;
|
|
112
|
+
return Math.min(20, Math.max(0, Math.trunc(value)));
|
|
113
|
+
}
|
|
95
114
|
registerHandler('channel.stop', (_ws, workspaceId, data) => {
|
|
96
115
|
const { channelId } = data;
|
|
97
116
|
if (!channelId)
|
|
@@ -115,6 +134,14 @@ registerHandler('agent.stop', (_ws, workspaceId, data) => {
|
|
|
115
134
|
to: 'completed',
|
|
116
135
|
});
|
|
117
136
|
});
|
|
137
|
+
// Workflow interaction response handler
|
|
138
|
+
registerHandler('workflow:interaction', (ws, _workspaceId, data) => {
|
|
139
|
+
const clientId = getClientId(ws);
|
|
140
|
+
if (clientId)
|
|
141
|
+
handleInteractionResponse(data, clientId);
|
|
142
|
+
});
|
|
143
|
+
// Register chat handlers
|
|
144
|
+
registerChatHandlers();
|
|
118
145
|
export { broadcastToWorkspace } from './connection-manager.js';
|
|
119
146
|
export { stopChannelRuns, hasActiveChannelRuns, markInactiveChannelRunsStopped } from './agent-runner.js';
|
|
120
147
|
//# sourceMappingURL=handler.js.map
|
package/dist/ws/message-parts.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
export function buildAgentMessageParts(input) {
|
|
2
|
-
const
|
|
2
|
+
const extractedReasoning = extractInlineReasoning(normalizeOutputLines(input.output));
|
|
3
|
+
const lines = extractedReasoning.lines;
|
|
3
4
|
const finalTextRange = findFinalTextRange(lines);
|
|
4
5
|
const finalText = finalTextRange
|
|
5
6
|
? collapseRepeatedTextBlock(lines.slice(finalTextRange.start, finalTextRange.end + 1)).join('\n').trim()
|
|
@@ -7,7 +8,10 @@ export function buildAgentMessageParts(input) {
|
|
|
7
8
|
const usage = input.usage ?? extractUsage(lines);
|
|
8
9
|
const parts = [];
|
|
9
10
|
const chainItems = buildChainItems(lines, finalTextRange, finalText, input.workspaceRoot, input.toolDetails, input.persistentContext, input.askUserQuestions);
|
|
10
|
-
const reasoningText = normalizeReasoningText(
|
|
11
|
+
const reasoningText = normalizeReasoningText([
|
|
12
|
+
...(input.reasoning ?? []),
|
|
13
|
+
...extractedReasoning.reasoning.map((text) => ({ text })),
|
|
14
|
+
]);
|
|
11
15
|
if (reasoningText) {
|
|
12
16
|
parts.push({
|
|
13
17
|
id: `reasoning-${input.sessionId}`,
|
|
@@ -84,16 +88,43 @@ export function normalizeOutputLines(output) {
|
|
|
84
88
|
.map((line) => line.trimEnd())
|
|
85
89
|
.filter((line) => line.trim());
|
|
86
90
|
const seenInitLines = new Set();
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
if (
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
91
|
+
let inAgentSpacesPromptEcho = false;
|
|
92
|
+
let skipNextUserMessageEchoLine = false;
|
|
93
|
+
const normalizedLines = [];
|
|
94
|
+
for (const line of lines) {
|
|
95
|
+
const cleaned = stripRuntimeNoticePrefix(stripRuntimeLogPrefix(line)).trim();
|
|
96
|
+
if (!cleaned)
|
|
97
|
+
continue;
|
|
98
|
+
if (skipNextUserMessageEchoLine) {
|
|
99
|
+
skipNextUserMessageEchoLine = false;
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
if (isAgentSpacesPromptEchoStart(cleaned) || /^Agent runtime configuration:\s*$/i.test(cleaned)) {
|
|
103
|
+
inAgentSpacesPromptEcho = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (inAgentSpacesPromptEcho) {
|
|
107
|
+
if (/\bUser message:\s*$/i.test(cleaned)) {
|
|
108
|
+
inAgentSpacesPromptEcho = false;
|
|
109
|
+
skipNextUserMessageEchoLine = true;
|
|
110
|
+
}
|
|
111
|
+
else if (/\bUser message:/i.test(cleaned)) {
|
|
112
|
+
inAgentSpacesPromptEcho = false;
|
|
113
|
+
}
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (isIgnorablePromptEchoLine(cleaned))
|
|
117
|
+
continue;
|
|
118
|
+
if (isIgnorableToolProgressLine(cleaned))
|
|
119
|
+
continue;
|
|
120
|
+
if (/^Claude Code initialized\b/i.test(cleaned)) {
|
|
121
|
+
if (seenInitLines.has(cleaned))
|
|
122
|
+
continue;
|
|
123
|
+
seenInitLines.add(cleaned);
|
|
124
|
+
}
|
|
125
|
+
normalizedLines.push(cleaned);
|
|
126
|
+
}
|
|
127
|
+
return normalizedLines;
|
|
97
128
|
}
|
|
98
129
|
export function mergeRuntimeOutput(liveOutput, resultOutput) {
|
|
99
130
|
if (liveOutput.length === 0)
|
|
@@ -192,6 +223,52 @@ function normalizeReasoningText(reasoning) {
|
|
|
192
223
|
.map((item) => item.text.trim())
|
|
193
224
|
.filter(Boolean)).join('\n\n').trim();
|
|
194
225
|
}
|
|
226
|
+
function extractInlineReasoning(lines) {
|
|
227
|
+
const reasoning = [];
|
|
228
|
+
const outputLines = [];
|
|
229
|
+
let buffer = [];
|
|
230
|
+
let inThinkBlock = false;
|
|
231
|
+
const flushReasoning = () => {
|
|
232
|
+
const text = buffer.join('\n').trim();
|
|
233
|
+
if (text)
|
|
234
|
+
reasoning.push(text);
|
|
235
|
+
buffer = [];
|
|
236
|
+
};
|
|
237
|
+
for (const line of lines) {
|
|
238
|
+
let rest = line;
|
|
239
|
+
let visible = '';
|
|
240
|
+
while (rest) {
|
|
241
|
+
if (inThinkBlock) {
|
|
242
|
+
const closeIndex = rest.search(/<\/think>/i);
|
|
243
|
+
if (closeIndex < 0) {
|
|
244
|
+
buffer.push(rest);
|
|
245
|
+
rest = '';
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
buffer.push(rest.slice(0, closeIndex));
|
|
249
|
+
flushReasoning();
|
|
250
|
+
rest = rest.slice(closeIndex).replace(/^<\/think>/i, '');
|
|
251
|
+
inThinkBlock = false;
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
const openIndex = rest.search(/<think>/i);
|
|
255
|
+
if (openIndex < 0) {
|
|
256
|
+
visible += rest;
|
|
257
|
+
rest = '';
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
visible += rest.slice(0, openIndex);
|
|
261
|
+
rest = rest.slice(openIndex).replace(/^<think>/i, '');
|
|
262
|
+
inThinkBlock = true;
|
|
263
|
+
}
|
|
264
|
+
const cleaned = visible.trimEnd();
|
|
265
|
+
if (cleaned.trim())
|
|
266
|
+
outputLines.push(cleaned);
|
|
267
|
+
}
|
|
268
|
+
if (inThinkBlock)
|
|
269
|
+
flushReasoning();
|
|
270
|
+
return { lines: outputLines, reasoning };
|
|
271
|
+
}
|
|
195
272
|
function hasTokenUsage(usage) {
|
|
196
273
|
return Boolean(usage.totalTokens || usage.inputTokens || usage.outputTokens || usage.cachedInputTokens || usage.reasoningTokens);
|
|
197
274
|
}
|
|
@@ -509,6 +586,47 @@ function isIgnorableToolProgressLine(line) {
|
|
|
509
586
|
|| (isSubagentProgress && /^(Reading|Searching)\s+\S.+$/i.test(trimmed))
|
|
510
587
|
|| /^(Read|Search|Grep|Glob|SemanticSearch|WebSearch)\s+running\s+\(\d+s\)$/i.test(trimmed);
|
|
511
588
|
}
|
|
589
|
+
function stripRuntimeNoticePrefix(line) {
|
|
590
|
+
let next = line.trim();
|
|
591
|
+
while (next.startsWith('[')) {
|
|
592
|
+
const closeIndex = next.indexOf(']');
|
|
593
|
+
if (closeIndex < 0)
|
|
594
|
+
break;
|
|
595
|
+
const label = next.slice(1, closeIndex);
|
|
596
|
+
if (!isStrippableRuntimeNoticeLabel(label))
|
|
597
|
+
break;
|
|
598
|
+
next = next.slice(closeIndex + 1).trimStart();
|
|
599
|
+
}
|
|
600
|
+
return next;
|
|
601
|
+
}
|
|
602
|
+
function stripRuntimeLogPrefix(line) {
|
|
603
|
+
return line.replace(/^\[[a-z0-9_-]+\]\s+(?:stdout|stderr)\s*\|\s*/i, '');
|
|
604
|
+
}
|
|
605
|
+
function isStrippableRuntimeNoticeLabel(label) {
|
|
606
|
+
return /^Tool loop warning:/i.test(label);
|
|
607
|
+
}
|
|
608
|
+
function isAgentSpacesPromptEchoStart(line) {
|
|
609
|
+
return /^-?\s*Agent Spaces channel tools configured for this channel:/i.test(line);
|
|
610
|
+
}
|
|
611
|
+
function isIgnorablePromptEchoLine(line) {
|
|
612
|
+
return /^-?\s*Code directories \(boundDirs\):/i.test(line)
|
|
613
|
+
|| /^-?\s*For Bash commands that create or modify files under the current working\b/i.test(line)
|
|
614
|
+
|| /^(directory,|paths\.)$/i.test(line)
|
|
615
|
+
|| /^When asked what MCP servers, skills, runtime tools, or Agent Spaces channel tools you have,/i.test(line)
|
|
616
|
+
|| /^When asked what MCP servers, skills, runtime tools, or Agent Spaces channel\b/i.test(line)
|
|
617
|
+
|| /^tools you have, answer from this configuration only\./i.test(line)
|
|
618
|
+
|| /^Important distinction: MCP servers configured for this agent are only the names in /i.test(line)
|
|
619
|
+
|| /^Important distinction: MCP servers configured for this agent are only the names\b/i.test(line)
|
|
620
|
+
|| /^in "MCP servers configured for this agent"\./i.test(line)
|
|
621
|
+
|| /^are$/i.test(line)
|
|
622
|
+
|| /^built-in runtime tools and must not be listed as agent-configured MCP servers\./i.test(line)
|
|
623
|
+
|| /^Do not infer availability from provider-side function names,/i.test(line)
|
|
624
|
+
|| /^Do not infer availability from provider-side function names, hidden runtime\b/i.test(line)
|
|
625
|
+
|| /^internals, previous sessions, or filesystem settings\./i.test(line)
|
|
626
|
+
|| /^-\s+(Current workspace id|MCP servers configured for this agent|Skills configured for this agent|Runtime tools available through Hermes):/i.test(line)
|
|
627
|
+
|| /^skills$/i.test(line)
|
|
628
|
+
|| /^User message:/i.test(line);
|
|
629
|
+
}
|
|
512
630
|
function stripSubagentProgressPrefix(line) {
|
|
513
631
|
return line.replace(/^\[[^\]]+\]\s+subagent progress\s+\|\s*/i, '');
|
|
514
632
|
}
|
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import * as ptyService from '../services/pty.js';
|
|
2
2
|
import { broadcastToWorkspace } from './connection-manager.js';
|
|
3
|
+
const DEBUG_TERMINAL_DUP = '[DEBUG-terminal-dup]';
|
|
3
4
|
export function sendTerminalSessions(ws, workspaceId) {
|
|
4
5
|
const existingSessions = ptyService.getSessionsByWorkspace(workspaceId);
|
|
6
|
+
console.log(DEBUG_TERMINAL_DUP, 'server sendTerminalSessions', {
|
|
7
|
+
workspaceId,
|
|
8
|
+
sessions: existingSessions.map(s => ({
|
|
9
|
+
sessionId: s.id,
|
|
10
|
+
bufferLength: s.buffer.join('').length,
|
|
11
|
+
})),
|
|
12
|
+
});
|
|
5
13
|
ws.send(JSON.stringify({
|
|
6
14
|
event: 'terminal.sessions',
|
|
7
15
|
workspaceId,
|
|
@@ -20,13 +28,31 @@ export function handleTerminalEvent(ws, workspaceId, event, data) {
|
|
|
20
28
|
case 'terminal.create': {
|
|
21
29
|
const payload = data;
|
|
22
30
|
const cwd = payload.cwd || process.env.HOME || process.env.USERPROFILE || (process.platform === 'win32' ? process.env.SYSTEMROOT || 'C:\\' : '/tmp');
|
|
31
|
+
console.log(DEBUG_TERMINAL_DUP, 'server terminal.create received', {
|
|
32
|
+
workspaceId,
|
|
33
|
+
sessionId: payload.sessionId,
|
|
34
|
+
shell: payload.shell,
|
|
35
|
+
cwd,
|
|
36
|
+
});
|
|
23
37
|
try {
|
|
24
38
|
const sessionId = ptyService.createSession(workspaceId, cwd, (id, output) => {
|
|
39
|
+
console.log(DEBUG_TERMINAL_DUP, 'server terminal.output broadcast', {
|
|
40
|
+
workspaceId,
|
|
41
|
+
sessionId: id,
|
|
42
|
+
outputLength: output.length,
|
|
43
|
+
preview: output.slice(0, 80),
|
|
44
|
+
});
|
|
25
45
|
broadcastToWorkspace(workspaceId, 'terminal.output', { sessionId: id, data: output });
|
|
26
46
|
}, (id, exitCode) => {
|
|
27
47
|
broadcastToWorkspace(workspaceId, 'terminal.closed', { sessionId: id, exitCode });
|
|
28
48
|
}, payload.shell, undefined, payload.sessionId);
|
|
29
49
|
const session = ptyService.getSession(sessionId);
|
|
50
|
+
console.log(DEBUG_TERMINAL_DUP, 'server terminal.created broadcast', {
|
|
51
|
+
workspaceId,
|
|
52
|
+
sessionId,
|
|
53
|
+
cwd,
|
|
54
|
+
shell: session?.shell ?? payload.shell,
|
|
55
|
+
});
|
|
30
56
|
broadcastToWorkspace(workspaceId, 'terminal.created', { sessionId, cwd, shell: session?.shell ?? payload.shell });
|
|
31
57
|
}
|
|
32
58
|
catch (err) {
|