@supen-ai/cli 1.4.0 → 1.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +1 -1
  2. package/daemon/dist/agent-sdk/app-server-stream.js +5 -5
  3. package/daemon/dist/agent-sdk/app-server-stream.js.map +1 -1
  4. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts +3 -2
  5. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.d.ts.map +1 -1
  6. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js +72 -49
  7. package/daemon/dist/agent-sdk/drivers/codex-app-server-driver.js.map +1 -1
  8. package/daemon/dist/agent-sdk/drivers/driver.d.ts +8 -8
  9. package/daemon/dist/agent-sdk/drivers/driver.d.ts.map +1 -1
  10. package/daemon/dist/agent-sdk/index.d.ts +4 -4
  11. package/daemon/dist/agent-sdk/index.d.ts.map +1 -1
  12. package/daemon/dist/agent-sdk/index.js +2 -2
  13. package/daemon/dist/agent-sdk/index.js.map +1 -1
  14. package/daemon/dist/agent-sdk/intelligence/contracts.d.ts +2 -2
  15. package/daemon/dist/agent-sdk/intelligence/contracts.d.ts.map +1 -1
  16. package/daemon/dist/agent-sdk/memory/subsystem.d.ts +1 -1
  17. package/daemon/dist/agent-sdk/memory/subsystem.d.ts.map +1 -1
  18. package/daemon/dist/agent-sdk/{session-events.d.ts → thread-events.d.ts} +18 -18
  19. package/daemon/dist/agent-sdk/thread-events.d.ts.map +1 -0
  20. package/daemon/dist/agent-sdk/{session-events.js → thread-events.js} +15 -15
  21. package/daemon/dist/agent-sdk/thread-events.js.map +1 -0
  22. package/daemon/dist/agent-sdk/{session-manager.d.ts → thread-manager.d.ts} +9 -9
  23. package/daemon/dist/agent-sdk/thread-manager.d.ts.map +1 -0
  24. package/daemon/dist/agent-sdk/{session-manager.js → thread-manager.js} +7 -7
  25. package/daemon/dist/agent-sdk/thread-manager.js.map +1 -0
  26. package/daemon/dist/agent-sdk/types.d.ts +18 -18
  27. package/daemon/dist/agent-sdk/types.d.ts.map +1 -1
  28. package/daemon/dist/automation-event-listener.js +6 -6
  29. package/daemon/dist/automation-event-listener.js.map +1 -1
  30. package/daemon/dist/automation-runner.d.ts +1 -1
  31. package/daemon/dist/automation-runner.d.ts.map +1 -1
  32. package/daemon/dist/automation-runner.js +24 -24
  33. package/daemon/dist/automation-runner.js.map +1 -1
  34. package/daemon/dist/autonomy/memory-rules.d.ts +2 -2
  35. package/daemon/dist/autonomy/memory-rules.d.ts.map +1 -1
  36. package/daemon/dist/autonomy/memory-rules.js +1 -1
  37. package/daemon/dist/autonomy/memory-rules.js.map +1 -1
  38. package/daemon/dist/autonomy/session-autonomy.d.ts +4 -4
  39. package/daemon/dist/autonomy/session-autonomy.d.ts.map +1 -1
  40. package/daemon/dist/autonomy/session-autonomy.js +5 -5
  41. package/daemon/dist/autonomy/session-autonomy.js.map +1 -1
  42. package/daemon/dist/bin/mcp-os.js +1 -1
  43. package/daemon/dist/bin/mcp-os.js.map +1 -1
  44. package/daemon/dist/bin/mcp-scheduler.js +1 -1
  45. package/daemon/dist/bin/mcp-scheduler.js.map +1 -1
  46. package/daemon/dist/bin/supen-sys.js +1 -1
  47. package/daemon/dist/bin/supen-sys.js.map +1 -1
  48. package/daemon/dist/bootstrap/hub-bootstrap.js +1 -1
  49. package/daemon/dist/bootstrap/hub-bootstrap.js.map +1 -1
  50. package/daemon/dist/channels/http-routes.d.ts.map +1 -1
  51. package/daemon/dist/channels/http-routes.js +54 -60
  52. package/daemon/dist/channels/http-routes.js.map +1 -1
  53. package/daemon/dist/channels/http.js +2 -2
  54. package/daemon/dist/channels/http.js.map +1 -1
  55. package/daemon/dist/channels/index.d.ts +0 -1
  56. package/daemon/dist/channels/index.d.ts.map +1 -1
  57. package/daemon/dist/channels/index.js +0 -2
  58. package/daemon/dist/channels/index.js.map +1 -1
  59. package/daemon/dist/channels/registry.d.ts +1 -1
  60. package/daemon/dist/channels/registry.d.ts.map +1 -1
  61. package/daemon/dist/commands/builtin.d.ts +1 -1
  62. package/daemon/dist/commands/builtin.d.ts.map +1 -1
  63. package/daemon/dist/commands/builtin.js +2 -2
  64. package/daemon/dist/commands/builtin.js.map +1 -1
  65. package/daemon/dist/commands/catalog.js +3 -3
  66. package/daemon/dist/commands/catalog.js.map +1 -1
  67. package/daemon/dist/core/config.d.ts +0 -2
  68. package/daemon/dist/core/config.d.ts.map +1 -1
  69. package/daemon/dist/core/config.js +0 -5
  70. package/daemon/dist/core/config.js.map +1 -1
  71. package/daemon/dist/core/control-commands.d.ts +3 -3
  72. package/daemon/dist/core/control-commands.d.ts.map +1 -1
  73. package/daemon/dist/core/control-commands.js +6 -6
  74. package/daemon/dist/core/control-commands.js.map +1 -1
  75. package/daemon/dist/core/control-log.d.ts +4 -4
  76. package/daemon/dist/core/control-log.d.ts.map +1 -1
  77. package/daemon/dist/core/control-log.js +12 -12
  78. package/daemon/dist/core/control-log.js.map +1 -1
  79. package/daemon/dist/core/cortex.d.ts +4 -4
  80. package/daemon/dist/core/cortex.d.ts.map +1 -1
  81. package/daemon/dist/core/cortex.js +98 -117
  82. package/daemon/dist/core/cortex.js.map +1 -1
  83. package/daemon/dist/core/dispatcher.d.ts +3 -3
  84. package/daemon/dist/core/dispatcher.js +18 -18
  85. package/daemon/dist/core/dispatcher.js.map +1 -1
  86. package/daemon/dist/core/gateway-protocol.d.ts +0 -1
  87. package/daemon/dist/core/gateway-protocol.d.ts.map +1 -1
  88. package/daemon/dist/core/gateway.d.ts +3 -3
  89. package/daemon/dist/core/gateway.d.ts.map +1 -1
  90. package/daemon/dist/core/gateway.js +26 -26
  91. package/daemon/dist/core/gateway.js.map +1 -1
  92. package/daemon/dist/core/hub-snapshot.d.ts +1 -1
  93. package/daemon/dist/core/hub-snapshot.d.ts.map +1 -1
  94. package/daemon/dist/core/hub-snapshot.js +1 -1
  95. package/daemon/dist/core/hub-snapshot.js.map +1 -1
  96. package/daemon/dist/core/pairing.d.ts +2 -2
  97. package/daemon/dist/core/pairing.js +3 -3
  98. package/daemon/dist/core/pairing.js.map +1 -1
  99. package/daemon/dist/core/store.d.ts +38 -38
  100. package/daemon/dist/core/store.d.ts.map +1 -1
  101. package/daemon/dist/core/store.js +285 -289
  102. package/daemon/dist/core/store.js.map +1 -1
  103. package/daemon/dist/core/task-artifacts.d.ts +4 -4
  104. package/daemon/dist/core/task-artifacts.d.ts.map +1 -1
  105. package/daemon/dist/core/task-artifacts.js +10 -10
  106. package/daemon/dist/core/task-artifacts.js.map +1 -1
  107. package/daemon/dist/core/thread-context.d.ts +76 -0
  108. package/daemon/dist/core/thread-context.d.ts.map +1 -0
  109. package/daemon/dist/core/thread-context.js +308 -0
  110. package/daemon/dist/core/thread-context.js.map +1 -0
  111. package/daemon/dist/core/types.d.ts +28 -28
  112. package/daemon/dist/core/types.d.ts.map +1 -1
  113. package/daemon/dist/core/utils.js +1 -1
  114. package/daemon/dist/core/utils.js.map +1 -1
  115. package/daemon/dist/http/router.d.ts +2 -2
  116. package/daemon/dist/http/router.d.ts.map +1 -1
  117. package/daemon/dist/http/router.js +5 -5
  118. package/daemon/dist/http/router.js.map +1 -1
  119. package/daemon/dist/http/routes/agents.js +3 -3
  120. package/daemon/dist/http/routes/agents.js.map +1 -1
  121. package/daemon/dist/http/routes/automations.d.ts +2 -2
  122. package/daemon/dist/http/routes/automations.d.ts.map +1 -1
  123. package/daemon/dist/http/routes/automations.js +23 -23
  124. package/daemon/dist/http/routes/automations.js.map +1 -1
  125. package/daemon/dist/http/routes/chat-input.d.ts +1 -1
  126. package/daemon/dist/http/routes/chat-input.d.ts.map +1 -1
  127. package/daemon/dist/http/routes/chat-input.js +2 -2
  128. package/daemon/dist/http/routes/chat-input.js.map +1 -1
  129. package/daemon/dist/http/routes/plugins.d.ts.map +1 -1
  130. package/daemon/dist/http/routes/plugins.js +6 -74
  131. package/daemon/dist/http/routes/plugins.js.map +1 -1
  132. package/daemon/dist/http/routes/rpc.d.ts +3 -3
  133. package/daemon/dist/http/routes/rpc.d.ts.map +1 -1
  134. package/daemon/dist/http/routes/rpc.js +93 -92
  135. package/daemon/dist/http/routes/rpc.js.map +1 -1
  136. package/daemon/dist/http/routes/system.d.ts +8 -7
  137. package/daemon/dist/http/routes/system.d.ts.map +1 -1
  138. package/daemon/dist/http/routes/system.js +225 -111
  139. package/daemon/dist/http/routes/system.js.map +1 -1
  140. package/daemon/dist/http/routes/threads.d.ts +11 -0
  141. package/daemon/dist/http/routes/threads.d.ts.map +1 -0
  142. package/daemon/dist/http/routes/{sessions.js → threads.js} +158 -158
  143. package/daemon/dist/http/routes/threads.js.map +1 -0
  144. package/daemon/dist/http/stream.d.ts +2 -2
  145. package/daemon/dist/http/stream.d.ts.map +1 -1
  146. package/daemon/dist/http/stream.js +3 -3
  147. package/daemon/dist/http/stream.js.map +1 -1
  148. package/daemon/dist/http/thread-title.d.ts +1 -1
  149. package/daemon/dist/http/thread-title.d.ts.map +1 -1
  150. package/daemon/dist/http/thread-title.js +6 -6
  151. package/daemon/dist/http/thread-title.js.map +1 -1
  152. package/daemon/dist/http/websocket.d.ts +2 -2
  153. package/daemon/dist/http/websocket.d.ts.map +1 -1
  154. package/daemon/dist/http/websocket.js +11 -11
  155. package/daemon/dist/http/websocket.js.map +1 -1
  156. package/daemon/dist/index.d.ts +3 -3
  157. package/daemon/dist/index.d.ts.map +1 -1
  158. package/daemon/dist/index.js +65 -81
  159. package/daemon/dist/index.js.map +1 -1
  160. package/daemon/dist/mcp/aggregate-config.d.ts +1 -1
  161. package/daemon/dist/mcp/index.js +1 -1
  162. package/daemon/dist/mcp/index.js.map +1 -1
  163. package/daemon/dist/mcp/tools.d.ts +1 -1
  164. package/daemon/dist/mcp/tools.js +1 -1
  165. package/daemon/dist/plugins/hub.d.ts +2 -8
  166. package/daemon/dist/plugins/hub.d.ts.map +1 -1
  167. package/daemon/dist/plugins/hub.js +63 -214
  168. package/daemon/dist/plugins/hub.js.map +1 -1
  169. package/daemon/dist/plugins/types.d.ts +10 -0
  170. package/daemon/dist/plugins/types.d.ts.map +1 -1
  171. package/daemon/dist/sub-agent.d.ts +3 -3
  172. package/daemon/dist/sub-agent.d.ts.map +1 -1
  173. package/daemon/dist/sub-agent.js +8 -8
  174. package/daemon/dist/sub-agent.js.map +1 -1
  175. package/daemon/dist/sync/supabase-sync.js +18 -18
  176. package/daemon/dist/sync/supabase-sync.js.map +1 -1
  177. package/daemon/dist/task-executor.js +1 -1
  178. package/daemon/dist/task-executor.js.map +1 -1
  179. package/daemon/dist/tools/shell.js +1 -1
  180. package/daemon/dist/tools/shell.js.map +1 -1
  181. package/daemon/dist/tools/types.d.ts +1 -1
  182. package/daemon/dist/tools/types.d.ts.map +1 -1
  183. package/daemon/package.json +1 -1
  184. package/dist/computer.js +1 -1
  185. package/dist/index.js +1 -1
  186. package/package.json +1 -1
  187. package/daemon/dist/acp-client.d.ts +0 -42
  188. package/daemon/dist/acp-client.d.ts.map +0 -1
  189. package/daemon/dist/acp-client.js +0 -149
  190. package/daemon/dist/acp-client.js.map +0 -1
  191. package/daemon/dist/acp-types.d.ts +0 -98
  192. package/daemon/dist/acp-types.d.ts.map +0 -1
  193. package/daemon/dist/acp-types.js +0 -2
  194. package/daemon/dist/acp-types.js.map +0 -1
  195. package/daemon/dist/agent-sdk/session-events.d.ts.map +0 -1
  196. package/daemon/dist/agent-sdk/session-events.js.map +0 -1
  197. package/daemon/dist/agent-sdk/session-manager.d.ts.map +0 -1
  198. package/daemon/dist/agent-sdk/session-manager.js.map +0 -1
  199. package/daemon/dist/channels/acp.d.ts +0 -23
  200. package/daemon/dist/channels/acp.d.ts.map +0 -1
  201. package/daemon/dist/channels/acp.js +0 -915
  202. package/daemon/dist/channels/acp.js.map +0 -1
  203. package/daemon/dist/http/routes/sessions.d.ts +0 -11
  204. package/daemon/dist/http/routes/sessions.d.ts.map +0 -1
  205. package/daemon/dist/http/routes/sessions.js.map +0 -1
@@ -1,915 +0,0 @@
1
- import fs from 'fs';
2
- import path from 'path';
3
- import { randomUUID } from 'crypto';
4
- import { ACP_ENABLED, ACP_RELAY_URL } from '../core/config.js';
5
- import { ensureSession } from '../core/store.js';
6
- import { logger } from '../core/logger.js';
7
- import { validateOutboundEndpoint } from '../core/security.js';
8
- import { submitTask } from '../task-executor.js';
9
- import { ACPClient } from '../acp-client.js';
10
- import { registerChannel } from './registry.js';
11
- // NOTE: This ACP channel is the external transport surface for ACP clients that
12
- // talk to Supen as a daemon/channel endpoint. It is intentionally separate from
13
- // the Codex app-server agent transport. We should not collapse coding-agent
14
- // transport into a "channel" abstraction here.
15
- const ACP_JID_PREFIX = 'acp:';
16
- const MAX_SESSION_MESSAGES = 200;
17
- let runtimeOpts = null;
18
- let defaultClient = null;
19
- const sessionsById = new Map();
20
- const sessionIdByChatJid = new Map();
21
- function nowIso() {
22
- return new Date().toISOString();
23
- }
24
- function buildThreadId(agentId, sessionId) {
25
- return `${encodeURIComponent(agentId)}:${encodeURIComponent(sessionId)}`;
26
- }
27
- function parseThreadId(threadId) {
28
- const parts = threadId.split(':');
29
- if (parts.length < 2)
30
- return null;
31
- try {
32
- return {
33
- agent_id: decodeURIComponent(parts[0]),
34
- session_id: decodeURIComponent(parts[1]),
35
- };
36
- }
37
- catch {
38
- return null;
39
- }
40
- }
41
- export function buildAcpChatJid(agentId, sessionId) {
42
- return `${ACP_JID_PREFIX}${buildThreadId(agentId, sessionId)}`;
43
- }
44
- function readJsonBody(req) {
45
- return new Promise((resolve, reject) => {
46
- let body = '';
47
- req.setEncoding('utf-8');
48
- req.on('data', (chunk) => {
49
- body += chunk;
50
- });
51
- req.on('end', () => {
52
- if (!body.trim()) {
53
- resolve({});
54
- return;
55
- }
56
- try {
57
- resolve(JSON.parse(body));
58
- }
59
- catch {
60
- reject(new Error('Invalid JSON body'));
61
- }
62
- });
63
- req.on('error', reject);
64
- });
65
- }
66
- function writeJson(res, statusCode, payload) {
67
- res.writeHead(statusCode, {
68
- 'Content-Type': 'application/json',
69
- });
70
- res.end(JSON.stringify(payload));
71
- }
72
- function writeProtocolError(res, statusCode, code, message, details) {
73
- writeJson(res, statusCode, {
74
- error: {
75
- code,
76
- message,
77
- ...(details ? { details } : {}),
78
- },
79
- });
80
- }
81
- function wantsSse(req, url) {
82
- if (url.searchParams.get('stream') === '1')
83
- return true;
84
- const accept = req.headers.accept;
85
- return typeof accept === 'string' && accept.includes('text/event-stream');
86
- }
87
- function inferMimeType(filePath) {
88
- const ext = path.extname(filePath).toLowerCase();
89
- if (ext === '.md')
90
- return 'text/markdown';
91
- if (ext === '.base')
92
- return 'text/yaml';
93
- if (ext === '.txt' || ext === '.log' || ext === '.jsonl')
94
- return 'text/plain';
95
- if (ext === '.json')
96
- return 'application/json';
97
- if (ext === '.png')
98
- return 'image/png';
99
- if (ext === '.jpg' || ext === '.jpeg')
100
- return 'image/jpeg';
101
- if (ext === '.webp')
102
- return 'image/webp';
103
- if (ext === '.pdf')
104
- return 'application/pdf';
105
- return undefined;
106
- }
107
- function normalizeContentPart(part) {
108
- if (!part)
109
- return null;
110
- if (typeof part === 'string') {
111
- return { type: 'text', text: part };
112
- }
113
- if (typeof part !== 'object')
114
- return null;
115
- const value = part;
116
- const type = value.type;
117
- if ((type === 'text' || type === 'markdown') &&
118
- typeof value.text === 'string') {
119
- return { type, text: value.text };
120
- }
121
- if (type === 'artifact') {
122
- return {
123
- type: 'artifact',
124
- name: typeof value.name === 'string' ? value.name : undefined,
125
- mime_type: typeof value.mime_type === 'string' ? value.mime_type : undefined,
126
- uri: typeof value.uri === 'string' ? value.uri : undefined,
127
- path: typeof value.path === 'string' ? value.path : undefined,
128
- description: typeof value.description === 'string' ? value.description : undefined,
129
- data: typeof value.data === 'string' ? value.data : undefined,
130
- };
131
- }
132
- return null;
133
- }
134
- function normalizeContent(content) {
135
- if (typeof content === 'string') {
136
- return content.trim() ? [{ type: 'text', text: content }] : [];
137
- }
138
- if (Array.isArray(content)) {
139
- return content
140
- .map((part) => normalizeContentPart(part))
141
- .filter((part) => !!part);
142
- }
143
- if (content && typeof content === 'object') {
144
- const single = normalizeContentPart(content);
145
- return single ? [single] : [];
146
- }
147
- return [];
148
- }
149
- function normalizeToolCall(toolCall) {
150
- if (!toolCall || typeof toolCall !== 'object')
151
- return null;
152
- const value = toolCall;
153
- const name = typeof value.name === 'string'
154
- ? value.name
155
- : typeof value.tool_name === 'string'
156
- ? value.tool_name
157
- : null;
158
- if (!name)
159
- return null;
160
- return {
161
- id: typeof value.id === 'string' && value.id
162
- ? value.id
163
- : typeof value.tool_call_id === 'string' && value.tool_call_id
164
- ? value.tool_call_id
165
- : randomUUID(),
166
- name,
167
- arguments: value.arguments && typeof value.arguments === 'object'
168
- ? value.arguments
169
- : value.input && typeof value.input === 'object'
170
- ? value.input
171
- : undefined,
172
- };
173
- }
174
- function normalizeToolResult(toolResult) {
175
- if (!toolResult || typeof toolResult !== 'object')
176
- return null;
177
- const value = toolResult;
178
- const toolCallId = typeof value.tool_call_id === 'string'
179
- ? value.tool_call_id
180
- : typeof value.id === 'string'
181
- ? value.id
182
- : null;
183
- if (!toolCallId)
184
- return null;
185
- return {
186
- tool_call_id: toolCallId,
187
- name: typeof value.name === 'string' ? value.name : undefined,
188
- result: value.result ?? value.output ?? value.content,
189
- is_error: typeof value.is_error === 'boolean'
190
- ? value.is_error
191
- : typeof value.error === 'boolean'
192
- ? value.error
193
- : false,
194
- };
195
- }
196
- export function normalizeAcpEnvelope(input, fallbackRole = 'user') {
197
- const raw = input && typeof input === 'object' && 'message' in input
198
- ? input.message
199
- : input;
200
- const value = raw && typeof raw === 'object' ? raw : {};
201
- const role = value.role === 'system' ||
202
- value.role === 'user' ||
203
- value.role === 'assistant' ||
204
- value.role === 'tool'
205
- ? value.role
206
- : fallbackRole;
207
- const content = normalizeContent(value.content ?? value.parts ?? value.text ?? value.prompt ?? '');
208
- const toolCalls = Array.isArray(value.tool_calls)
209
- ? value.tool_calls
210
- .map((toolCall) => normalizeToolCall(toolCall))
211
- .filter((toolCall) => !!toolCall)
212
- : [];
213
- const toolResults = Array.isArray(value.tool_results)
214
- ? value.tool_results
215
- .map((toolResult) => normalizeToolResult(toolResult))
216
- .filter((toolResult) => !!toolResult)
217
- : [];
218
- return {
219
- id: typeof value.id === 'string' && value.id ? value.id : randomUUID(),
220
- role,
221
- content,
222
- ...(toolCalls.length ? { tool_calls: toolCalls } : {}),
223
- ...(toolResults.length ? { tool_results: toolResults } : {}),
224
- ...(value.metadata && typeof value.metadata === 'object'
225
- ? { metadata: value.metadata }
226
- : {}),
227
- created_at: typeof value.created_at === 'string' && value.created_at
228
- ? value.created_at
229
- : nowIso(),
230
- };
231
- }
232
- function renderContentForPrompt(content) {
233
- return content
234
- .map((part) => {
235
- if (part.type === 'artifact') {
236
- return [
237
- '[Artifact]',
238
- part.name ? `name: ${part.name}` : null,
239
- part.path ? `path: ${part.path}` : null,
240
- part.uri ? `uri: ${part.uri}` : null,
241
- part.description ? `description: ${part.description}` : null,
242
- ]
243
- .filter(Boolean)
244
- .join('\n');
245
- }
246
- return part.text;
247
- })
248
- .filter(Boolean)
249
- .join('\n\n');
250
- }
251
- function buildPromptFromEnvelope(envelope, mode = 'message') {
252
- const parts = [];
253
- if (mode === 'tools' && envelope.tool_calls?.length) {
254
- parts.push('Execute the requested tool calls and return the results clearly. Include any errors inline.');
255
- }
256
- else {
257
- parts.push(`ACP ${envelope.role} message:`);
258
- }
259
- const renderedContent = renderContentForPrompt(envelope.content);
260
- if (renderedContent)
261
- parts.push(renderedContent);
262
- if (envelope.tool_calls?.length) {
263
- parts.push(`[ACP tool calls]\n${JSON.stringify(envelope.tool_calls, null, 2)}`);
264
- }
265
- if (envelope.tool_results?.length) {
266
- parts.push(`[ACP tool results]\n${JSON.stringify(envelope.tool_results, null, 2)}`);
267
- }
268
- return parts.join('\n\n').trim();
269
- }
270
- function isExecutableEnvelope(envelope) {
271
- if (envelope.tool_calls?.length)
272
- return true;
273
- return envelope.content.some((part) => part.type === 'artifact' ? true : !!part.text.trim());
274
- }
275
- function serializeSession(session) {
276
- return {
277
- id: session.id,
278
- thread_id: session.thread_id,
279
- agent_id: session.agent_id,
280
- session_id: session.session_id,
281
- chat_jid: session.chat_jid,
282
- created_at: session.created_at,
283
- updated_at: session.updated_at,
284
- stream_url: session.stream_url,
285
- message_url: session.message_url,
286
- tools_url: session.tools_url,
287
- last_task_id: session.last_task_id || null,
288
- metadata: session.metadata || null,
289
- };
290
- }
291
- function writeSseEvent(res, event, eventName) {
292
- if (eventName) {
293
- res.write(`event: ${eventName}\n`);
294
- }
295
- res.write(`data: ${JSON.stringify(event)}\n\n`);
296
- }
297
- function broadcastSessionEvent(session, event, eventName) {
298
- for (const client of [...session.clients]) {
299
- try {
300
- writeSseEvent(client, event, eventName);
301
- }
302
- catch {
303
- session.clients.delete(client);
304
- }
305
- }
306
- }
307
- function appendMessage(session, envelope) {
308
- session.messages.push(envelope);
309
- if (session.messages.length > MAX_SESSION_MESSAGES) {
310
- session.messages.splice(0, session.messages.length - MAX_SESSION_MESSAGES);
311
- }
312
- session.updated_at = nowIso();
313
- }
314
- function listAgentManifests() {
315
- const opts = runtimeOpts;
316
- const uniqueAgents = new Map();
317
- for (const project of Object.values(opts?.registeredProjects() || {})) {
318
- const agentId = project.agent_id || project.folder;
319
- uniqueAgents.set(agentId, agentId);
320
- }
321
- if (uniqueAgents.size === 0) {
322
- uniqueAgents.set('default', 'default');
323
- }
324
- return [...uniqueAgents.keys()].map((agentId) => ({
325
- id: buildThreadId(agentId, agentId),
326
- protocol: 'acp',
327
- name: `Supen ${agentId}`,
328
- description: 'Supen ACP-compatible agent endpoint',
329
- version: '1.0.0',
330
- agent_id: agentId,
331
- endpoints: {
332
- agents: '/acp/agents',
333
- sessions: '/acp/sessions',
334
- },
335
- capabilities: {
336
- streaming: true,
337
- inbound: true,
338
- outbound: true,
339
- tool_calls: true,
340
- tool_results: true,
341
- content_types: ['text', 'markdown', 'artifact'],
342
- },
343
- }));
344
- }
345
- function ensureRegisteredProject(agentId, chatJid, timestamp) {
346
- const opts = runtimeOpts;
347
- if (!opts)
348
- return;
349
- const projects = opts.registeredProjects();
350
- if (!projects[chatJid] && opts.onGroupRegistered) {
351
- const project = {
352
- name: agentId,
353
- folder: agentId,
354
- agent_id: agentId,
355
- trigger: '',
356
- added_at: timestamp,
357
- requiresTrigger: false,
358
- isMain: false,
359
- };
360
- opts.onGroupRegistered(chatJid, project);
361
- }
362
- opts.onChatMetadata(chatJid, timestamp, undefined, 'acp', false);
363
- }
364
- function ensureRelaySync(session) {
365
- if (!defaultClient || session.relay_sync_started)
366
- return;
367
- session.relay_sync_started = true;
368
- void defaultClient
369
- .createSession({
370
- agent_id: session.agent_id,
371
- session_id: session.session_id,
372
- thread_id: session.thread_id,
373
- metadata: {
374
- origin: 'supen',
375
- local_session_id: session.id,
376
- },
377
- })
378
- .then(({ session: remoteSession }) => {
379
- session.relay_session_id = remoteSession.id;
380
- session.relay_abort = new AbortController();
381
- return defaultClient.streamSession(remoteSession.id, {
382
- onEvent: async (event) => {
383
- await handleRelayEvent(session, event);
384
- },
385
- }, session.relay_abort.signal);
386
- })
387
- .catch((err) => {
388
- logger.warn({ err, session_id: session.id, relay_url: ACP_RELAY_URL }, 'ACP relay sync unavailable');
389
- });
390
- }
391
- async function syncEnvelopeToRelay(session, envelope) {
392
- if (!defaultClient || !session.relay_session_id)
393
- return;
394
- try {
395
- await defaultClient.sendMessage(session.relay_session_id, {
396
- message: {
397
- ...envelope,
398
- metadata: {
399
- ...(envelope.metadata || {}),
400
- origin: 'supen',
401
- local_session_id: session.id,
402
- },
403
- },
404
- });
405
- }
406
- catch (err) {
407
- logger.warn({ err, session_id: session.id }, 'Failed to sync ACP message');
408
- }
409
- }
410
- async function syncToolCallsToRelay(session, toolCalls) {
411
- if (!defaultClient || !session.relay_session_id || toolCalls.length === 0)
412
- return;
413
- try {
414
- await defaultClient.sendToolCalls(session.relay_session_id, {
415
- tool_calls: toolCalls,
416
- metadata: {
417
- origin: 'supen',
418
- local_session_id: session.id,
419
- },
420
- });
421
- }
422
- catch (err) {
423
- logger.warn({ err, session_id: session.id }, 'Failed to sync ACP tool calls');
424
- }
425
- }
426
- async function handleRelayEvent(session, event) {
427
- const origin = event.message?.metadata && typeof event.message.metadata.origin === 'string'
428
- ? event.message.metadata.origin
429
- : undefined;
430
- if (origin === 'supen')
431
- return;
432
- if (event.type === 'message' && event.message) {
433
- appendMessage(session, event.message);
434
- broadcastSessionEvent(session, {
435
- type: 'message',
436
- session_id: session.id,
437
- task_id: session.last_task_id,
438
- message: event.message,
439
- });
440
- if (event.message.role !== 'assistant' ||
441
- event.message.tool_calls?.length) {
442
- await submitEnvelope(session, event.message, 'acp-relay', 'message', false);
443
- }
444
- return;
445
- }
446
- if (event.type === 'tool_call' && event.tool_calls?.length) {
447
- const envelope = {
448
- id: randomUUID(),
449
- role: 'user',
450
- content: [],
451
- tool_calls: event.tool_calls,
452
- created_at: nowIso(),
453
- metadata: { origin: 'acp-relay' },
454
- };
455
- await submitEnvelope(session, envelope, 'acp-relay', 'tools', false);
456
- return;
457
- }
458
- if (event.type === 'tool_result' && event.tool_results?.length) {
459
- const envelope = {
460
- id: randomUUID(),
461
- role: 'tool',
462
- content: [],
463
- tool_results: event.tool_results,
464
- created_at: nowIso(),
465
- metadata: { origin: 'acp-relay' },
466
- };
467
- appendMessage(session, envelope);
468
- broadcastSessionEvent(session, {
469
- type: 'tool_result',
470
- session_id: session.id,
471
- task_id: session.last_task_id,
472
- tool_results: event.tool_results,
473
- message: envelope,
474
- });
475
- }
476
- }
477
- function ensureAcpSessionState(input) {
478
- const parsedThread = input.thread_id ? parseThreadId(input.thread_id) : null;
479
- const agentId = parsedThread?.agent_id || input.agent_id;
480
- const sessionId = parsedThread?.session_id ||
481
- input.session_id ||
482
- input.thread_id ||
483
- randomUUID();
484
- const threadId = buildThreadId(agentId, sessionId);
485
- const existing = sessionsById.get(threadId);
486
- if (existing) {
487
- if (input.metadata) {
488
- existing.metadata = {
489
- ...(existing.metadata || {}),
490
- ...input.metadata,
491
- };
492
- }
493
- existing.updated_at = nowIso();
494
- return existing;
495
- }
496
- const chatJid = buildAcpChatJid(agentId, sessionId);
497
- const timestamp = nowIso();
498
- ensureRegisteredProject(agentId, chatJid, timestamp);
499
- ensureSession({
500
- agent_id: agentId,
501
- session_id: sessionId,
502
- channel: 'acp',
503
- agent_name: agentId,
504
- });
505
- const session = {
506
- id: threadId,
507
- thread_id: threadId,
508
- agent_id: agentId,
509
- session_id: sessionId,
510
- chat_jid: chatJid,
511
- created_at: timestamp,
512
- updated_at: timestamp,
513
- stream_url: `/acp/sessions/${encodeURIComponent(threadId)}?stream=1`,
514
- message_url: `/acp/sessions/${encodeURIComponent(threadId)}`,
515
- tools_url: `/acp/sessions/${encodeURIComponent(threadId)}/tools`,
516
- metadata: input.metadata,
517
- messages: [],
518
- clients: new Set(),
519
- };
520
- sessionsById.set(threadId, session);
521
- sessionIdByChatJid.set(chatJid, threadId);
522
- ensureRelaySync(session);
523
- return session;
524
- }
525
- function getSessionByRouteId(id) {
526
- const decoded = decodeURIComponent(id);
527
- const existing = sessionsById.get(decoded);
528
- if (existing)
529
- return existing;
530
- const parsed = parseThreadId(decoded);
531
- if (!parsed)
532
- return null;
533
- return ensureAcpSessionState(parsed);
534
- }
535
- async function submitEnvelope(session, envelope, actorId, mode = 'message', syncToHub = true) {
536
- appendMessage(session, envelope);
537
- broadcastSessionEvent(session, {
538
- type: 'message',
539
- session_id: session.id,
540
- task_id: session.last_task_id,
541
- message: envelope,
542
- });
543
- if (syncToHub) {
544
- await syncEnvelopeToRelay(session, envelope);
545
- }
546
- if (!isExecutableEnvelope(envelope)) {
547
- return { accepted: false };
548
- }
549
- const task = submitTask({
550
- agent_id: session.agent_id,
551
- session_id: session.session_id,
552
- prompt: buildPromptFromEnvelope(envelope, mode),
553
- source: 'acp',
554
- submitted_by: actorId,
555
- submitter_type: 'api_key',
556
- metadata: {
557
- protocol: 'acp',
558
- mode,
559
- envelope,
560
- },
561
- });
562
- session.last_task_id = task.id;
563
- session.updated_at = nowIso();
564
- broadcastSessionEvent(session, {
565
- type: 'task',
566
- session_id: session.id,
567
- task_id: task.id,
568
- data: {
569
- phase: 'queued',
570
- agent_id: session.agent_id,
571
- session_id: session.session_id,
572
- },
573
- });
574
- return {
575
- accepted: true,
576
- task_id: task.id,
577
- };
578
- }
579
- export async function publishAcpTaskEvent(chatJid, event) {
580
- const sessionId = sessionIdByChatJid.get(chatJid);
581
- if (!sessionId)
582
- return;
583
- const session = sessionsById.get(sessionId);
584
- if (!session)
585
- return;
586
- const phase = typeof event.phase === 'string' ? event.phase : 'activity';
587
- const payload = {
588
- type: 'task',
589
- session_id: session.id,
590
- task_id: typeof event.task_id === 'string' ? event.task_id : session.last_task_id,
591
- data: event,
592
- };
593
- if (phase === 'message_delta') {
594
- broadcastSessionEvent(session, {
595
- type: 'message.delta',
596
- session_id: session.id,
597
- task_id: payload.task_id,
598
- data: event,
599
- });
600
- return;
601
- }
602
- if (phase === 'tool_call' && Array.isArray(event.tool_calls)) {
603
- const toolCalls = event.tool_calls;
604
- broadcastSessionEvent(session, {
605
- type: 'tool_call',
606
- session_id: session.id,
607
- task_id: payload.task_id,
608
- tool_calls: toolCalls,
609
- data: event,
610
- });
611
- await syncToolCallsToRelay(session, toolCalls);
612
- return;
613
- }
614
- if (phase === 'tool_result' && Array.isArray(event.tool_results)) {
615
- broadcastSessionEvent(session, {
616
- type: 'tool_result',
617
- session_id: session.id,
618
- task_id: payload.task_id,
619
- tool_results: event.tool_results,
620
- data: event,
621
- });
622
- return;
623
- }
624
- if (phase === 'failed' || phase === 'timeout' || phase === 'canceled') {
625
- broadcastSessionEvent(session, {
626
- type: 'error',
627
- session_id: session.id,
628
- task_id: payload.task_id,
629
- error: {
630
- code: event.error &&
631
- typeof event.error === 'object' &&
632
- typeof event.error.code === 'string'
633
- ? event.error.code
634
- : phase,
635
- message: event.error &&
636
- typeof event.error === 'object' &&
637
- typeof event.error.message === 'string'
638
- ? event.error.message
639
- : `Task ${phase}`,
640
- },
641
- data: event,
642
- });
643
- return;
644
- }
645
- broadcastSessionEvent(session, payload);
646
- }
647
- export async function maybeHandleAcpRequest(req, res, url) {
648
- if (!url.pathname.startsWith('/acp'))
649
- return false;
650
- if (!ACP_ENABLED) {
651
- writeProtocolError(res, 503, 'acp_disabled', 'ACP channel is disabled');
652
- return true;
653
- }
654
- try {
655
- if (url.pathname === '/acp/agents' && req.method === 'GET') {
656
- writeJson(res, 200, {
657
- protocol: 'acp',
658
- agents: listAgentManifests(),
659
- });
660
- return true;
661
- }
662
- if (url.pathname === '/acp/sessions' && req.method === 'POST') {
663
- const parsed = await readJsonBody(req);
664
- const agentId = typeof parsed.agent_id === 'string' && parsed.agent_id.trim()
665
- ? parsed.agent_id.trim()
666
- : '';
667
- if (!agentId) {
668
- writeProtocolError(res, 400, 'agent_id_required', 'agent_id is required');
669
- return true;
670
- }
671
- const session = ensureAcpSessionState({
672
- agent_id: agentId,
673
- session_id: typeof parsed.session_id === 'string' ? parsed.session_id : undefined,
674
- thread_id: typeof parsed.thread_id === 'string' ? parsed.thread_id : undefined,
675
- metadata: parsed.metadata && typeof parsed.metadata === 'object'
676
- ? parsed.metadata
677
- : undefined,
678
- });
679
- let taskId;
680
- if (parsed.message) {
681
- const envelope = normalizeAcpEnvelope(parsed.message);
682
- const result = await submitEnvelope(session, envelope, 'acp');
683
- taskId = result.task_id;
684
- }
685
- writeJson(res, taskId ? 202 : 201, {
686
- session: serializeSession(session),
687
- task_id: taskId || null,
688
- });
689
- return true;
690
- }
691
- const sessionToolsMatch = url.pathname.match(/^\/acp\/sessions\/([^/]+)\/tools$/);
692
- if (sessionToolsMatch && req.method === 'POST') {
693
- const session = getSessionByRouteId(sessionToolsMatch[1]);
694
- if (!session) {
695
- writeProtocolError(res, 404, 'session_not_found', 'ACP session not found');
696
- return true;
697
- }
698
- const parsed = await readJsonBody(req);
699
- const toolCalls = Array.isArray(parsed.tool_calls)
700
- ? parsed.tool_calls
701
- .map((toolCall) => normalizeToolCall(toolCall))
702
- .filter((toolCall) => !!toolCall)
703
- : [];
704
- if (toolCalls.length === 0) {
705
- writeProtocolError(res, 400, 'tool_calls_required', 'tool_calls must contain at least one tool call');
706
- return true;
707
- }
708
- const envelope = {
709
- id: randomUUID(),
710
- role: 'user',
711
- content: normalizeContent(parsed.content ?? parsed.prompt ?? ''),
712
- tool_calls: toolCalls,
713
- created_at: nowIso(),
714
- ...(parsed.metadata && typeof parsed.metadata === 'object'
715
- ? { metadata: parsed.metadata }
716
- : {}),
717
- };
718
- const result = await submitEnvelope(session, envelope, 'acp', 'tools');
719
- writeJson(res, 202, {
720
- session: serializeSession(session),
721
- task_id: result.task_id || null,
722
- message: envelope,
723
- });
724
- return true;
725
- }
726
- const sessionMatch = url.pathname.match(/^\/acp\/sessions\/([^/]+)$/);
727
- if (sessionMatch) {
728
- const session = getSessionByRouteId(sessionMatch[1]);
729
- if (!session) {
730
- writeProtocolError(res, 404, 'session_not_found', 'ACP session not found');
731
- return true;
732
- }
733
- if (req.method === 'GET') {
734
- if (wantsSse(req, url)) {
735
- res.writeHead(200, {
736
- 'Content-Type': 'text/event-stream',
737
- 'Cache-Control': 'no-cache',
738
- Connection: 'keep-alive',
739
- 'X-Accel-Buffering': 'no',
740
- });
741
- session.clients.add(res);
742
- writeSseEvent(res, {
743
- type: 'session',
744
- session_id: session.id,
745
- task_id: session.last_task_id,
746
- data: {
747
- session: serializeSession(session),
748
- messages: session.messages.slice(-20),
749
- },
750
- }, 'session');
751
- const heartbeat = setInterval(() => {
752
- try {
753
- res.write(': ping\n\n');
754
- }
755
- catch {
756
- clearInterval(heartbeat);
757
- }
758
- }, 20_000);
759
- req.on('close', () => {
760
- clearInterval(heartbeat);
761
- session.clients.delete(res);
762
- });
763
- return true;
764
- }
765
- writeJson(res, 200, {
766
- session: serializeSession(session),
767
- messages: session.messages,
768
- });
769
- return true;
770
- }
771
- if (req.method === 'POST') {
772
- const parsed = await readJsonBody(req);
773
- const envelope = normalizeAcpEnvelope(parsed, 'user');
774
- const result = await submitEnvelope(session, envelope, 'acp');
775
- writeJson(res, result.task_id ? 202 : 200, {
776
- session: serializeSession(session),
777
- task_id: result.task_id || null,
778
- message: envelope,
779
- });
780
- return true;
781
- }
782
- }
783
- }
784
- catch (err) {
785
- const statusCode = err && typeof err === 'object' && 'statusCode' in err
786
- ? Number(err.statusCode) || 500
787
- : 500;
788
- const code = err && typeof err === 'object' && 'code' in err
789
- ? String(err.code)
790
- : 'internal_error';
791
- const message = err instanceof Error ? err.message : String(err);
792
- logger.error({ err, pathname: url.pathname }, 'ACP request failed');
793
- writeProtocolError(res, statusCode, code, message);
794
- return true;
795
- }
796
- return false;
797
- }
798
- export class AcpChannel {
799
- name = 'acp';
800
- opts;
801
- connected = false;
802
- constructor(opts) {
803
- this.opts = opts;
804
- runtimeOpts = opts;
805
- if (ACP_RELAY_URL) {
806
- try {
807
- const trustedEndpoint = validateOutboundEndpoint(ACP_RELAY_URL, {
808
- allowedProtocols: ['http:', 'https:'],
809
- label: 'ACP_RELAY_URL',
810
- });
811
- defaultClient = new ACPClient({
812
- baseUrl: trustedEndpoint.toString(),
813
- });
814
- }
815
- catch (err) {
816
- logger.error({ err: err.message, acp_relay_url: ACP_RELAY_URL }, 'ACP channel disabled remote sync due to endpoint security policy');
817
- defaultClient = null;
818
- }
819
- }
820
- }
821
- async connect() {
822
- this.connected = true;
823
- logger.info({
824
- relay_url: ACP_RELAY_URL || null,
825
- }, 'ACP channel ready');
826
- }
827
- async sendMessage(jid, text, _options) {
828
- const sessionId = sessionIdByChatJid.get(jid);
829
- if (!sessionId)
830
- return;
831
- const session = sessionsById.get(sessionId);
832
- if (!session || !text.trim())
833
- return;
834
- const envelope = {
835
- id: randomUUID(),
836
- role: 'assistant',
837
- content: [{ type: 'markdown', text }],
838
- created_at: nowIso(),
839
- };
840
- appendMessage(session, envelope);
841
- broadcastSessionEvent(session, {
842
- type: 'message',
843
- session_id: session.id,
844
- task_id: session.last_task_id,
845
- message: envelope,
846
- });
847
- await syncEnvelopeToRelay(session, envelope);
848
- }
849
- async sendFile(jid, filePath, caption) {
850
- const sessionId = sessionIdByChatJid.get(jid);
851
- if (!sessionId)
852
- return;
853
- const session = sessionsById.get(sessionId);
854
- if (!session)
855
- return;
856
- const artifactPart = {
857
- type: 'artifact',
858
- name: path.basename(filePath),
859
- path: filePath,
860
- mime_type: inferMimeType(filePath),
861
- description: caption,
862
- };
863
- if (fs.existsSync(filePath)) {
864
- artifactPart.uri = filePath;
865
- }
866
- const envelope = {
867
- id: randomUUID(),
868
- role: 'assistant',
869
- content: [
870
- ...(caption ? [{ type: 'markdown', text: caption }] : []),
871
- artifactPart,
872
- ],
873
- created_at: nowIso(),
874
- };
875
- appendMessage(session, envelope);
876
- broadcastSessionEvent(session, {
877
- type: 'message',
878
- session_id: session.id,
879
- task_id: session.last_task_id,
880
- message: envelope,
881
- });
882
- await syncEnvelopeToRelay(session, envelope);
883
- }
884
- isConnected() {
885
- return this.connected;
886
- }
887
- ownsJid(jid) {
888
- return jid.startsWith(ACP_JID_PREFIX);
889
- }
890
- async disconnect() {
891
- this.connected = false;
892
- for (const session of sessionsById.values()) {
893
- session.relay_abort?.abort();
894
- for (const client of session.clients) {
895
- try {
896
- client.end();
897
- }
898
- catch {
899
- /* ignore */
900
- }
901
- }
902
- session.clients.clear();
903
- }
904
- logger.info('ACP channel disconnected');
905
- }
906
- }
907
- function createAcpChannel(opts) {
908
- if (!ACP_ENABLED) {
909
- logger.debug('ACP channel disabled (ACP_ENABLED=false)');
910
- return null;
911
- }
912
- return new AcpChannel(opts);
913
- }
914
- registerChannel('acp', createAcpChannel);
915
- //# sourceMappingURL=acp.js.map