@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.
Files changed (64) hide show
  1. package/dist/adapters/agent-runtime.js +4 -0
  2. package/dist/adapters/codex-function-tool-bridge.js +9 -1
  3. package/dist/adapters/git.js +13 -2
  4. package/dist/adapters/hermes-runtime.js +711 -108
  5. package/dist/adapters/langchain-runtime.js +384 -21
  6. package/dist/adapters/oh-my-pi-runtime.js +858 -0
  7. package/dist/adapters/open-agent-sdk-runtime.js +202 -5
  8. package/dist/adapters/open-agent-sdk-runtime.test.js +35 -0
  9. package/dist/agents/agent-message-parts.js +52 -2
  10. package/dist/agents/issue-task-controller.js +4 -2
  11. package/dist/agents/title-generator-agent.js +120 -0
  12. package/dist/app.js +59 -6
  13. package/dist/routes/agent-sse.js +70 -7
  14. package/dist/routes/channel.js +23 -2
  15. package/dist/routes/chat-run.js +191 -0
  16. package/dist/routes/chat.js +142 -0
  17. package/dist/routes/command.js +16 -0
  18. package/dist/routes/data.js +189 -0
  19. package/dist/routes/git.js +10 -1
  20. package/dist/routes/import.js +199 -0
  21. package/dist/routes/issue.js +16 -4
  22. package/dist/routes/plugin.js +69 -0
  23. package/dist/routes/version.js +2 -1
  24. package/dist/routes/workflow-hook.js +71 -0
  25. package/dist/routes/workflow.js +282 -7
  26. package/dist/routes/workspace.js +13 -4
  27. package/dist/services/agent.js +123 -36
  28. package/dist/services/ai-text.js +185 -0
  29. package/dist/services/builtin-tools/index.js +1 -0
  30. package/dist/services/builtin-tools/workflow-editor-tools.js +509 -0
  31. package/dist/services/builtin-tools/workflow-exec-tools.js +320 -0
  32. package/dist/services/chat.js +134 -0
  33. package/dist/services/command-process-manager.js +16 -0
  34. package/dist/services/execution-manager.js +1346 -0
  35. package/dist/services/generated-title.js +59 -0
  36. package/dist/services/gitignore.js +22 -18
  37. package/dist/services/interaction-manager.js +114 -0
  38. package/dist/services/issue-retry.js +25 -0
  39. package/dist/services/output-style.js +8 -1
  40. package/dist/services/plugin.js +257 -0
  41. package/dist/services/prompt-template.js +8 -1
  42. package/dist/services/pty.js +20 -0
  43. package/dist/services/search.js +16 -6
  44. package/dist/services/skill.js +2 -0
  45. package/dist/services/version.js +17 -7
  46. package/dist/services/workflow-command-runner.js +5 -4
  47. package/dist/services/workflow-trigger-service.js +137 -0
  48. package/dist/services/workflow.js +199 -36
  49. package/dist/storage/agent-store.js +8 -0
  50. package/dist/storage/chat-store.js +151 -0
  51. package/dist/storage/database-store.js +6 -0
  52. package/dist/storage/json-store.js +2 -1
  53. package/dist/storage/kanban-store.js +6 -0
  54. package/dist/storage/workflow-store.js +386 -22
  55. package/dist/ws/agent-prompt.js +6 -2
  56. package/dist/ws/agent-runner.js +29 -18
  57. package/dist/ws/chat-handler.js +89 -0
  58. package/dist/ws/connection-manager.js +49 -2
  59. package/dist/ws/execution-channels.js +88 -0
  60. package/dist/ws/handler.js +32 -5
  61. package/dist/ws/message-parts.js +130 -12
  62. package/dist/ws/terminal-handler.js +26 -0
  63. package/package.json +12 -1
  64. package/public/avatars/user.jpg +0 -0
@@ -1,12 +1,13 @@
1
1
  import { spawn } from 'node:child_process';
2
- import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync } from 'node:fs';
3
- import { basename, extname, join } from 'node:path';
2
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs';
3
+ import { basename, delimiter, extname, join } from 'node:path';
4
4
  import { appendOutputStyleToPrompt, summarizeResult } from './agent-runtime-types.js';
5
+ import { startCodexFunctionToolBridge } from './codex-function-tool-bridge.js';
5
6
  /**
6
7
  * Runtime backed by the external Hermes CLI.
7
8
  *
8
9
  * Hermes does not currently expose a structured JS SDK here, so this adapter
9
- * treats verbose CLI output as the source of truth and streams it as text.
10
+ * classifies CLI text output before forwarding it to the UI.
10
11
  */
11
12
  export class HermesRuntime {
12
13
  config;
@@ -18,115 +19,145 @@ export class HermesRuntime {
18
19
  const output = [];
19
20
  const cwd = workingDir || process.cwd();
20
21
  const startTime = Date.now();
21
- const d = (message) => console.log(`[hermes] ${message}`);
22
+ const d = (message) => console.log(`[hermes] ${truncateConsoleMessage(message)}`);
22
23
  const agentDir = options?.configDir;
23
24
  const hermesHome = agentDir ? join(agentDir, '.hermes') : undefined;
24
- if (hermesHome)
25
- prepareHermesHome(hermesHome, agentDir);
26
- const args = buildHermesArgs(appendOutputStyleToPrompt(prompt, options?.outputStyle), this.config, options);
27
- d(`starting | cwd=${cwd} model=${this.config.model ?? 'profile-default'} provider=${this.config.provider ?? 'profile-default'} hermesHome=${hermesHome ?? 'default'} skills=${options?.skills?.join(',') || '-'}`);
28
- return new Promise((resolve) => {
29
- let settled = false;
30
- let stdoutBuffer = '';
31
- let stderrBuffer = '';
32
- let sessionId = options?.resumeSessionId;
33
- let emittedSessionId = sessionId;
34
- const finish = (result) => {
35
- if (settled)
25
+ let functionToolBridge;
26
+ try {
27
+ functionToolBridge = await startCodexFunctionToolBridge(options?.functionTools, d);
28
+ const mcpServers = withFunctionToolBridge(options?.mcpServers, functionToolBridge);
29
+ if (hermesHome)
30
+ prepareHermesHome(hermesHome, this.config, agentDir, options?.skills, mcpServers, d);
31
+ const args = buildHermesArgs(appendOutputStyleToPrompt(prompt, options?.outputStyle), this.config, options);
32
+ const cliProvider = getHermesCliProvider(this.config.provider);
33
+ d(`starting | cwd=${cwd} model=${this.config.model ?? 'profile-default'} provider=${cliProvider ?? 'profile-default'} requestedProvider=${this.config.provider ?? 'profile-default'} hermesHome=${hermesHome ?? 'default'} mcpServers=${Object.keys(mcpServers ?? {}).join(',') || '-'} functionToolBridge=${functionToolBridge?.url ?? '-'} skills=${options?.skills?.join(',') || '-'}`);
34
+ return await new Promise((resolve) => {
35
+ let settled = false;
36
+ let stdoutBuffer = '';
37
+ let stderrBuffer = '';
38
+ let sessionId = options?.resumeSessionId;
39
+ let emittedSessionId = sessionId;
40
+ const lineParser = createHermesLineParser(output, options);
41
+ const finish = (result) => {
42
+ if (settled)
43
+ return;
44
+ settled = true;
45
+ this.child = null;
46
+ resolve(result);
47
+ };
48
+ try {
49
+ this.child = spawn(resolveHermesCommand(), args, {
50
+ cwd,
51
+ env: buildEnv(this.config, hermesHome),
52
+ windowsHide: true,
53
+ });
54
+ }
55
+ catch (err) {
56
+ const message = err instanceof Error ? err.message : String(err);
57
+ d(`failed to start | ${message}`);
58
+ finish({
59
+ success: false,
60
+ summary: 'Hermes execution failed',
61
+ artifacts: [],
62
+ error: message,
63
+ output,
64
+ sessionId,
65
+ });
36
66
  return;
37
- settled = true;
38
- this.child = null;
39
- resolve(result);
40
- };
41
- try {
42
- this.child = spawn('hermes', args, {
43
- cwd,
44
- env: buildEnv(this.config, hermesHome),
45
- windowsHide: true,
67
+ }
68
+ this.child.stdout.setEncoding('utf-8');
69
+ this.child.stderr.setEncoding('utf-8');
70
+ this.child.stdout.on('data', (chunk) => {
71
+ stdoutBuffer = consumeLines(stdoutBuffer + chunk, (line) => {
72
+ if (!line)
73
+ return;
74
+ d(`stdout | ${line}`);
75
+ const parsedSessionId = extractSessionId(line);
76
+ if (parsedSessionId && parsedSessionId !== emittedSessionId) {
77
+ sessionId = parsedSessionId;
78
+ emittedSessionId = parsedSessionId;
79
+ options?.onEvent?.({ type: 'session', sessionId });
80
+ }
81
+ lineParser.handleLine(line, 'stdout');
82
+ });
46
83
  });
47
- }
48
- catch (err) {
49
- const message = err instanceof Error ? err.message : String(err);
50
- d(`failed to start | ${message}`);
51
- finish({
52
- success: false,
53
- summary: 'Hermes execution failed',
54
- artifacts: [],
55
- error: message,
56
- output,
57
- sessionId,
84
+ this.child.stderr.on('data', (chunk) => {
85
+ stderrBuffer = consumeLines(stderrBuffer + chunk, (line) => {
86
+ if (!line)
87
+ return;
88
+ d(`stderr | ${line}`);
89
+ lineParser.handleLine(line, 'stderr');
90
+ });
58
91
  });
59
- return;
60
- }
61
- this.child.stdout.setEncoding('utf-8');
62
- this.child.stderr.setEncoding('utf-8');
63
- this.child.stdout.on('data', (chunk) => {
64
- stdoutBuffer = consumeLines(stdoutBuffer + chunk, (line) => {
65
- if (!line)
66
- return;
67
- output.push(line);
68
- const parsedSessionId = extractSessionId(line);
69
- if (parsedSessionId && parsedSessionId !== emittedSessionId) {
70
- sessionId = parsedSessionId;
71
- emittedSessionId = parsedSessionId;
72
- options?.onEvent?.({ type: 'session', sessionId });
73
- }
74
- options?.onEvent?.({ type: 'output', line });
92
+ this.child.on('error', (err) => {
93
+ const message = err.message.includes('ENOENT')
94
+ ? 'Hermes CLI was not found. Install Hermes and ensure the `hermes` command is available on PATH.'
95
+ : err.message;
96
+ d(`failed ${Date.now() - startTime}ms | ${message}`);
97
+ finish({
98
+ success: false,
99
+ summary: 'Hermes execution failed',
100
+ artifacts: [],
101
+ error: message,
102
+ output,
103
+ sessionId,
104
+ });
75
105
  });
76
- });
77
- this.child.stderr.on('data', (chunk) => {
78
- stderrBuffer = consumeLines(stderrBuffer + chunk, (line) => {
79
- if (!line)
106
+ this.child.on('close', (code, signal) => {
107
+ stdoutBuffer = flushLine(stdoutBuffer, (line) => lineParser.handleLine(line, 'stdout'));
108
+ stderrBuffer = flushLine(stderrBuffer, (line) => lineParser.handleLine(line, 'stderr'));
109
+ const elapsed = Date.now() - startTime;
110
+ if (code === 0) {
111
+ const text = lastMeaningfulLine(output);
112
+ d(`done ${elapsed}ms`);
113
+ finish({
114
+ success: true,
115
+ summary: summarizeResult(text),
116
+ artifacts: [],
117
+ output,
118
+ sessionId,
119
+ });
80
120
  return;
81
- const formatted = `[stderr] ${line}`;
82
- output.push(formatted);
83
- options?.onEvent?.({ type: 'output', line: formatted });
84
- });
85
- });
86
- this.child.on('error', (err) => {
87
- const message = err.message.includes('ENOENT')
88
- ? 'Hermes CLI was not found. Install Hermes and ensure the `hermes` command is available on PATH.'
89
- : err.message;
90
- d(`failed ${Date.now() - startTime}ms | ${message}`);
91
- finish({
92
- success: false,
93
- summary: 'Hermes execution failed',
94
- artifacts: [],
95
- error: message,
96
- output,
97
- sessionId,
98
- });
99
- });
100
- this.child.on('close', (code, signal) => {
101
- stdoutBuffer = flushLine(stdoutBuffer, output, options);
102
- stderrBuffer = flushLine(stderrBuffer, output, options, '[stderr] ');
103
- const elapsed = Date.now() - startTime;
104
- if (code === 0) {
105
- const text = lastMeaningfulLine(output);
106
- d(`done ${elapsed}ms`);
121
+ }
122
+ const error = signal
123
+ ? `Hermes execution stopped by signal ${signal}`
124
+ : `Hermes execution failed with exit code ${code ?? 'unknown'}`;
125
+ d(`failed ${elapsed}ms | ${error}`);
107
126
  finish({
108
- success: true,
109
- summary: summarizeResult(text),
127
+ success: false,
128
+ summary: 'Hermes execution failed',
110
129
  artifacts: [],
130
+ error,
111
131
  output,
112
132
  sessionId,
113
133
  });
114
- return;
115
- }
116
- const error = signal
117
- ? `Hermes execution stopped by signal ${signal}`
118
- : `Hermes execution failed with exit code ${code ?? 'unknown'}`;
119
- d(`failed ${elapsed}ms | ${error}`);
120
- finish({
121
- success: false,
122
- summary: 'Hermes execution failed',
123
- artifacts: [],
124
- error,
125
- output,
126
- sessionId,
127
134
  });
128
135
  });
129
- });
136
+ }
137
+ catch (err) {
138
+ const elapsed = Date.now() - startTime;
139
+ const message = err instanceof Error ? err.message : String(err);
140
+ d(`failed ${elapsed}ms | ${message}`);
141
+ if (err instanceof Error && err.stack)
142
+ console.error(err.stack);
143
+ return {
144
+ success: false,
145
+ summary: 'Hermes execution failed',
146
+ artifacts: [],
147
+ error: message,
148
+ output,
149
+ sessionId: options?.resumeSessionId,
150
+ };
151
+ }
152
+ finally {
153
+ try {
154
+ await functionToolBridge?.close();
155
+ }
156
+ catch (err) {
157
+ const message = err instanceof Error ? err.message : String(err);
158
+ d(`function tool bridge close failed | ${message}`);
159
+ }
160
+ }
130
161
  }
131
162
  stop() {
132
163
  this.child?.kill();
@@ -138,14 +169,29 @@ function buildHermesArgs(prompt, config, options) {
138
169
  args.push('-s', skill);
139
170
  if (config.model)
140
171
  args.push('--model', config.model);
141
- if (config.provider)
142
- args.push('--provider', String(config.provider));
172
+ const provider = getHermesCliProvider(config.provider);
173
+ if (provider)
174
+ args.push('--provider', provider);
143
175
  return args;
144
176
  }
177
+ function getHermesCliProvider(provider) {
178
+ if (!provider || isAgentSpacesProtocolProvider(provider))
179
+ return undefined;
180
+ return String(provider);
181
+ }
182
+ function isAgentSpacesProtocolProvider(provider) {
183
+ return provider === 'anthropic-messages'
184
+ || provider === 'openai-chat-completions'
185
+ || provider === 'openai-responses'
186
+ || provider === 'openai-responses-to-anthropic-messages'
187
+ || provider === 'openai-chat-completions-to-anthropic-messages'
188
+ || provider === 'gemini-generate-content';
189
+ }
145
190
  function buildEnv(config, hermesHome) {
146
191
  const apiKey = config.apiKey || process.env.HERMES_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
147
192
  return {
148
193
  ...process.env,
194
+ AGENT_SPACES_HERMES_API_KEY: apiKey,
149
195
  HERMES_HOME: hermesHome || process.env.HERMES_HOME,
150
196
  HERMES_API_KEY: apiKey,
151
197
  HERMES_BASE_URL: config.baseURL || process.env.HERMES_BASE_URL,
@@ -155,8 +201,44 @@ function buildEnv(config, hermesHome) {
155
201
  NO_COLOR: process.env.NO_COLOR || '1',
156
202
  };
157
203
  }
158
- function prepareHermesHome(hermesHome, agentDir) {
204
+ function resolveHermesCommand() {
205
+ const configured = process.env.HERMES_CLI_PATH?.trim();
206
+ if (configured)
207
+ return configured;
208
+ if (process.platform !== 'win32')
209
+ return 'hermes';
210
+ const pathCommand = resolveWindowsPathCommand('hermes.exe');
211
+ if (pathCommand)
212
+ return pathCommand;
213
+ const candidates = [
214
+ process.env.LOCALAPPDATA
215
+ ? join(process.env.LOCALAPPDATA, 'hermes', 'hermes-agent', 'venv', 'Scripts', 'hermes.exe')
216
+ : undefined,
217
+ process.env.USERPROFILE
218
+ ? join(process.env.USERPROFILE, 'AppData', 'Local', 'hermes', 'hermes-agent', 'venv', 'Scripts', 'hermes.exe')
219
+ : undefined,
220
+ ].filter((candidate) => Boolean(candidate));
221
+ return candidates.find((candidate) => existsSync(candidate)) ?? 'hermes';
222
+ }
223
+ function resolveWindowsPathCommand(command) {
224
+ const pathValue = getPathEnvValue();
225
+ if (!pathValue)
226
+ return undefined;
227
+ for (const dir of pathValue.split(delimiter)) {
228
+ if (!dir.trim())
229
+ continue;
230
+ const candidate = join(dir.trim(), command);
231
+ if (existsSync(candidate))
232
+ return candidate;
233
+ }
234
+ return undefined;
235
+ }
236
+ function getPathEnvValue() {
237
+ return Object.entries(process.env).find(([key]) => key.toLowerCase() === 'path')?.[1];
238
+ }
239
+ function prepareHermesHome(hermesHome, config, agentDir, requestedSkills, mcpServers, log) {
159
240
  mkdirSync(hermesHome, { recursive: true });
241
+ writeManagedHermesConfig(hermesHome, config, mcpServers, log);
160
242
  const skillsDir = join(hermesHome, 'skills');
161
243
  rmSync(skillsDir, { recursive: true, force: true });
162
244
  mkdirSync(skillsDir, { recursive: true });
@@ -173,6 +255,273 @@ function prepareHermesHome(hermesHome, agentDir) {
173
255
  continue;
174
256
  copyFileSync(sourceFile, join(skillsDir, basename(file)));
175
257
  }
258
+ for (const skill of normalizeSkillNames(requestedSkills)) {
259
+ const sourceFile = join(sourceSkillsDir, `${skill}.md`);
260
+ const targetFile = join(skillsDir, `${skill}.md`);
261
+ if (existsSync(targetFile) || !existsSync(sourceFile) || !statSync(sourceFile).isFile())
262
+ continue;
263
+ copyFileSync(sourceFile, targetFile);
264
+ }
265
+ }
266
+ function writeManagedHermesConfig(hermesHome, config, mcpServers, log) {
267
+ const normalizedMcpServers = normalizeHermesMcpServers(mcpServers);
268
+ const hasModelConfig = Boolean(config.baseURL && config.model && isAgentSpacesProtocolProvider(config.provider));
269
+ if (!hasModelConfig && !normalizedMcpServers)
270
+ return;
271
+ const configPath = join(hermesHome, 'config.yaml');
272
+ if (existsSync(configPath)) {
273
+ const content = readFileSync(configPath, 'utf-8');
274
+ if (!isWritableAgentSpacesHermesConfigContent(content)) {
275
+ if (!normalizedMcpServers)
276
+ return;
277
+ const nextContent = mergeManagedHermesAgentSpacesConfig(content, normalizedMcpServers);
278
+ if (nextContent !== content) {
279
+ writeFileSync(configPath, nextContent, 'utf-8');
280
+ log?.(`merged Hermes MCP config | path=${configPath} servers=${Object.keys(normalizedMcpServers ?? {}).join(',') || '-'}`);
281
+ }
282
+ return;
283
+ }
284
+ }
285
+ const content = buildManagedHermesConfig(config, normalizedMcpServers);
286
+ writeFileSync(configPath, content, 'utf-8');
287
+ if (normalizedMcpServers) {
288
+ log?.(`wrote Hermes MCP config | path=${configPath} servers=${Object.keys(normalizedMcpServers).join(',')}`);
289
+ }
290
+ }
291
+ function buildManagedHermesConfig(config, normalizedMcpServers) {
292
+ const hasModelConfig = Boolean(config.baseURL && config.model && isAgentSpacesProtocolProvider(config.provider));
293
+ const lines = [
294
+ '# Managed by Agent Spaces for this agent profile.',
295
+ ];
296
+ if (hasModelConfig) {
297
+ const apiMode = getHermesApiMode(config.provider, config.baseURL);
298
+ lines.push('model:', ` default: ${yamlString(config.model)}`, ' provider: custom', ` base_url: ${yamlString(config.baseURL)}`, ' api_key: ${AGENT_SPACES_HERMES_API_KEY}');
299
+ if (apiMode)
300
+ lines.push(` api_mode: ${apiMode}`);
301
+ }
302
+ if (normalizedMcpServers) {
303
+ if (lines.length > 1)
304
+ lines.push('');
305
+ lines.push('mcp_servers:');
306
+ lines.push(...yamlMappingLines(normalizedMcpServers, 2));
307
+ }
308
+ return `${lines.join('\n')}\n`;
309
+ }
310
+ const AGENT_SPACES_MCP_START = ' # Agent Spaces managed MCP servers start';
311
+ const AGENT_SPACES_MCP_END = ' # Agent Spaces managed MCP servers end';
312
+ function mergeManagedHermesAgentSpacesConfig(content, normalizedMcpServers) {
313
+ return mergeManagedHermesMcpServers(content, normalizedMcpServers);
314
+ }
315
+ function mergeManagedHermesMcpServers(content, normalizedMcpServers) {
316
+ const lines = content.replace(/\r\n/g, '\n').split('\n');
317
+ if (lines.at(-1) === '')
318
+ lines.pop();
319
+ const mcpStart = lines.findIndex((line) => /^mcp_servers\s*:\s*(?:#.*)?$/.test(line));
320
+ const managedBlock = normalizedMcpServers
321
+ ? [AGENT_SPACES_MCP_START, ...yamlMappingLines(normalizedMcpServers, 2), AGENT_SPACES_MCP_END]
322
+ : [];
323
+ if (mcpStart === -1) {
324
+ if (!managedBlock.length)
325
+ return content;
326
+ const nextLines = [...lines];
327
+ if (nextLines.length && nextLines.at(-1)?.trim())
328
+ nextLines.push('');
329
+ nextLines.push('mcp_servers:', ...managedBlock);
330
+ return `${nextLines.join('\n')}\n`;
331
+ }
332
+ const mcpEnd = findNextTopLevelYamlKey(lines, mcpStart + 1);
333
+ const before = lines.slice(0, mcpStart + 1);
334
+ const mcpBody = removeManagedHermesMcpEntries(lines.slice(mcpStart + 1, mcpEnd), Object.keys(normalizedMcpServers ?? {}));
335
+ const after = lines.slice(mcpEnd);
336
+ const nextLines = [
337
+ ...before,
338
+ ...managedBlock,
339
+ ...mcpBody,
340
+ ...after,
341
+ ];
342
+ return `${nextLines.join('\n')}\n`;
343
+ }
344
+ function findNextTopLevelYamlKey(lines, startIndex) {
345
+ for (let index = startIndex; index < lines.length; index += 1) {
346
+ const line = lines[index];
347
+ if (!line.trim() || line.trimStart().startsWith('#'))
348
+ continue;
349
+ if (/^\S[^:]*:\s*/.test(line))
350
+ return index;
351
+ }
352
+ return lines.length;
353
+ }
354
+ function removeManagedHermesMcpEntries(lines, managedServerNames) {
355
+ const managedNameKeys = new Set(managedServerNames.flatMap((name) => [yamlKey(name), yamlString(name)]));
356
+ const result = [];
357
+ for (let index = 0; index < lines.length;) {
358
+ const line = lines[index];
359
+ if (line === AGENT_SPACES_MCP_START) {
360
+ index += 1;
361
+ while (index < lines.length && lines[index] !== AGENT_SPACES_MCP_END)
362
+ index += 1;
363
+ if (index < lines.length)
364
+ index += 1;
365
+ continue;
366
+ }
367
+ const entryKey = readIndentedYamlKey(line, 2);
368
+ if (entryKey && managedNameKeys.has(entryKey)) {
369
+ index += 1;
370
+ while (index < lines.length && !readIndentedYamlKey(lines[index], 2))
371
+ index += 1;
372
+ continue;
373
+ }
374
+ result.push(line);
375
+ index += 1;
376
+ }
377
+ return result;
378
+ }
379
+ function readIndentedYamlKey(line, indent) {
380
+ if (!line.startsWith(' '.repeat(indent)) || line.startsWith(' '.repeat(indent + 1)))
381
+ return undefined;
382
+ const match = line.slice(indent).match(/^((?:"(?:\\.|[^"])*")|[A-Za-z0-9_-]+)\s*:/);
383
+ return match?.[1];
384
+ }
385
+ function withFunctionToolBridge(mcpServers, bridge) {
386
+ if (!bridge)
387
+ return mcpServers;
388
+ return {
389
+ ...(mcpServers ?? {}),
390
+ [bridge.name]: { url: bridge.url },
391
+ };
392
+ }
393
+ function normalizeHermesMcpServers(servers) {
394
+ if (!servers || Object.keys(servers).length === 0)
395
+ return undefined;
396
+ const normalized = Object.fromEntries(Object.entries(servers).flatMap(([name, server]) => {
397
+ const normalizedServer = normalizeHermesMcpServer(server);
398
+ return normalizedServer ? [[name, normalizedServer]] : [];
399
+ }));
400
+ return Object.keys(normalized).length > 0 ? normalized : undefined;
401
+ }
402
+ function normalizeHermesMcpServer(server) {
403
+ if (!server || typeof server !== 'object' || Array.isArray(server))
404
+ return null;
405
+ const record = server;
406
+ if (typeof record.command === 'string') {
407
+ return removeUndefined({
408
+ command: record.command,
409
+ args: Array.isArray(record.args) ? record.args.map(String) : undefined,
410
+ env: isRecord(record.env) ? record.env : undefined,
411
+ ssl_verify: isBooleanOrString(record.ssl_verify) ? record.ssl_verify : undefined,
412
+ client_cert: isStringOrStringArray(record.client_cert) ? record.client_cert : undefined,
413
+ client_key: typeof record.client_key === 'string' ? record.client_key : undefined,
414
+ enabled: typeof record.enabled === 'boolean' ? record.enabled : true,
415
+ timeout: typeof record.timeout === 'number' ? record.timeout : undefined,
416
+ connect_timeout: typeof record.connect_timeout === 'number' ? record.connect_timeout : undefined,
417
+ supports_parallel_tool_calls: typeof record.supports_parallel_tool_calls === 'boolean'
418
+ ? record.supports_parallel_tool_calls
419
+ : undefined,
420
+ tools: isRecord(record.tools) ? record.tools : undefined,
421
+ sampling: isRecord(record.sampling) ? record.sampling : undefined,
422
+ });
423
+ }
424
+ if (typeof record.url === 'string') {
425
+ return removeUndefined({
426
+ url: record.url,
427
+ headers: isRecord(record.headers) ? record.headers : undefined,
428
+ ssl_verify: isBooleanOrString(record.ssl_verify) ? record.ssl_verify : undefined,
429
+ client_cert: isStringOrStringArray(record.client_cert) ? record.client_cert : undefined,
430
+ client_key: typeof record.client_key === 'string' ? record.client_key : undefined,
431
+ enabled: typeof record.enabled === 'boolean' ? record.enabled : true,
432
+ timeout: typeof record.timeout === 'number' ? record.timeout : undefined,
433
+ connect_timeout: typeof record.connect_timeout === 'number' ? record.connect_timeout : undefined,
434
+ supports_parallel_tool_calls: typeof record.supports_parallel_tool_calls === 'boolean'
435
+ ? record.supports_parallel_tool_calls
436
+ : undefined,
437
+ tools: isRecord(record.tools) ? record.tools : undefined,
438
+ auth: typeof record.auth === 'string' ? record.auth : undefined,
439
+ sampling: isRecord(record.sampling) ? record.sampling : undefined,
440
+ });
441
+ }
442
+ return null;
443
+ }
444
+ function yamlMappingLines(value, indent) {
445
+ return Object.entries(value).flatMap(([key, child]) => yamlEntryLines(key, child, indent));
446
+ }
447
+ function yamlEntryLines(key, value, indent) {
448
+ const prefix = ' '.repeat(indent);
449
+ if (Array.isArray(value)) {
450
+ if (value.length === 0)
451
+ return [`${prefix}${yamlKey(key)}: []`];
452
+ return [
453
+ `${prefix}${yamlKey(key)}:`,
454
+ ...value.map((item) => `${' '.repeat(indent + 2)}- ${yamlInlineValue(item)}`),
455
+ ];
456
+ }
457
+ if (isRecord(value)) {
458
+ const entries = yamlMappingLines(value, indent + 2);
459
+ return entries.length ? [`${prefix}${yamlKey(key)}:`, ...entries] : [`${prefix}${yamlKey(key)}: {}`];
460
+ }
461
+ return [`${prefix}${yamlKey(key)}: ${yamlInlineValue(value)}`];
462
+ }
463
+ function yamlInlineValue(value) {
464
+ if (typeof value === 'string')
465
+ return yamlString(value);
466
+ if (typeof value === 'number' || typeof value === 'boolean')
467
+ return String(value);
468
+ if (value === null)
469
+ return 'null';
470
+ return yamlString(JSON.stringify(value));
471
+ }
472
+ function yamlKey(value) {
473
+ return /^[A-Za-z0-9_-]+$/.test(value) ? value : yamlString(value);
474
+ }
475
+ function removeUndefined(record) {
476
+ return Object.fromEntries(Object.entries(record).filter(([, value]) => value !== undefined));
477
+ }
478
+ function isRecord(value) {
479
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
480
+ }
481
+ function isBooleanOrString(value) {
482
+ return typeof value === 'boolean' || typeof value === 'string';
483
+ }
484
+ function isStringOrStringArray(value) {
485
+ return typeof value === 'string' || (Array.isArray(value) && value.every((item) => typeof item === 'string'));
486
+ }
487
+ function isWritableAgentSpacesHermesConfigContent(content) {
488
+ return content.startsWith('# Managed by Agent Spaces for this agent profile.')
489
+ || content.includes('api_key: ${AGENT_SPACES_HERMES_API_KEY}');
490
+ }
491
+ function getHermesApiMode(provider, baseURL) {
492
+ switch (provider) {
493
+ case 'anthropic-messages':
494
+ return isAnthropicCompatibleBaseURL(baseURL) ? 'anthropic_messages' : 'chat_completions';
495
+ case 'openai-chat-completions':
496
+ case 'openai-chat-completions-to-anthropic-messages':
497
+ return 'chat_completions';
498
+ case 'openai-responses':
499
+ case 'openai-responses-to-anthropic-messages':
500
+ return 'codex_responses';
501
+ default:
502
+ return undefined;
503
+ }
504
+ }
505
+ function isAnthropicCompatibleBaseURL(baseURL) {
506
+ if (!baseURL)
507
+ return false;
508
+ try {
509
+ const url = new URL(baseURL);
510
+ return url.hostname.toLowerCase().endsWith('anthropic.com')
511
+ || url.pathname.toLowerCase().split('/').includes('anthropic');
512
+ }
513
+ catch {
514
+ return false;
515
+ }
516
+ }
517
+ function yamlString(value) {
518
+ return JSON.stringify(value);
519
+ }
520
+ function truncateConsoleMessage(message) {
521
+ const maxLength = 600;
522
+ if (message.length <= maxLength)
523
+ return message;
524
+ return `${message.slice(0, maxLength)}... [truncated ${message.length - maxLength} chars]`;
176
525
  }
177
526
  function consumeLines(buffer, onLine) {
178
527
  const lines = buffer.split(/\r?\n/);
@@ -181,13 +530,10 @@ function consumeLines(buffer, onLine) {
181
530
  onLine(stripAnsi(line).trimEnd());
182
531
  return remainder;
183
532
  }
184
- function flushLine(buffer, output, options, prefix = '') {
533
+ function flushLine(buffer, onLine) {
185
534
  const line = stripAnsi(buffer).trimEnd();
186
- if (line) {
187
- const formatted = `${prefix}${line}`;
188
- output.push(formatted);
189
- options?.onEvent?.({ type: 'output', line: formatted });
190
- }
535
+ if (line)
536
+ onLine(line);
191
537
  return '';
192
538
  }
193
539
  function extractSessionId(line) {
@@ -202,9 +548,266 @@ function normalizeSkillNames(skills) {
202
548
  .filter(Boolean);
203
549
  }
204
550
  function lastMeaningfulLine(output) {
205
- return [...output].reverse().find((line) => line.trim() && !line.startsWith('[stderr]')) ?? '';
551
+ return [...output].reverse().find((line) => line.trim() && !isDiagnosticOutputLine(line)) ?? '';
206
552
  }
207
553
  function stripAnsi(value) {
208
554
  return value.replace(/\u001b\[[0-9;]*m/g, '');
209
555
  }
556
+ function createHermesLineParser(output, options) {
557
+ let inEchoedQuery = false;
558
+ let inRuntimeConfig = false;
559
+ let inUserMessageEcho = false;
560
+ let inVerboseArgsBlock = false;
561
+ let inVerboseResultBlock = false;
562
+ let inVerboseThinkingBlock = false;
563
+ let inCapturedReasoningBlock = false;
564
+ let toolUseIndex = 0;
565
+ let emittedUsage = false;
566
+ const emitOutput = (line) => {
567
+ output.push(line);
568
+ options?.onEvent?.({ type: 'output', line });
569
+ };
570
+ const emitReasoning = (text) => {
571
+ const normalized = text.trim();
572
+ if (!normalized)
573
+ return;
574
+ options?.onEvent?.({ type: 'reasoning', text: normalized, status: 'completed' });
575
+ };
576
+ const emitUsage = (usageLine) => {
577
+ if (emittedUsage)
578
+ return;
579
+ emittedUsage = true;
580
+ emitOutput(usageLine);
581
+ };
582
+ const emitToolUse = (tool) => {
583
+ toolUseIndex += 1;
584
+ const id = `hermes-tool-${toolUseIndex}`;
585
+ const line = tool.rawInput ? `Tool: ${tool.name} ${tool.rawInput}` : `Tool: ${tool.name}`;
586
+ options?.onEvent?.({
587
+ type: 'tool_use',
588
+ id,
589
+ name: tool.name,
590
+ input: tool.input,
591
+ line,
592
+ });
593
+ };
594
+ return {
595
+ handleLine(line, source) {
596
+ const normalized = line.trim();
597
+ if (!normalized)
598
+ return;
599
+ if (inCapturedReasoningBlock) {
600
+ if (source === 'stderr' && !isHermesLogLine(normalized)) {
601
+ emitReasoning(normalized);
602
+ return;
603
+ }
604
+ if (source === 'stderr')
605
+ inCapturedReasoningBlock = false;
606
+ }
607
+ const capturedReasoning = parseCapturedReasoningStart(normalized);
608
+ if (capturedReasoning !== undefined) {
609
+ if (capturedReasoning)
610
+ emitReasoning(capturedReasoning);
611
+ inCapturedReasoningBlock = true;
612
+ return;
613
+ }
614
+ const toolUse = parseHermesToolUse(normalized);
615
+ if (toolUse) {
616
+ emitToolUse(toolUse);
617
+ return;
618
+ }
619
+ const usageLine = parseHermesUsageLine(normalized);
620
+ if (usageLine) {
621
+ emitUsage(usageLine);
622
+ return;
623
+ }
624
+ const reasoning = parseHermesReasoningLine(normalized);
625
+ if (reasoning) {
626
+ emitReasoning(reasoning);
627
+ return;
628
+ }
629
+ if (source === 'stderr')
630
+ return;
631
+ if (inVerboseThinkingBlock) {
632
+ if (!isHermesThinkingContinuationLine(normalized)) {
633
+ inVerboseThinkingBlock = false;
634
+ }
635
+ else {
636
+ emitReasoning(normalized);
637
+ return;
638
+ }
639
+ }
640
+ if (inVerboseResultBlock) {
641
+ if (isHermesVerboseBlockBoundaryLine(normalized))
642
+ inVerboseResultBlock = false;
643
+ else {
644
+ if (normalized.endsWith('}'))
645
+ inVerboseResultBlock = false;
646
+ return;
647
+ }
648
+ }
649
+ if (inVerboseArgsBlock) {
650
+ if (normalized === '}')
651
+ inVerboseArgsBlock = false;
652
+ return;
653
+ }
654
+ if (inUserMessageEcho)
655
+ return;
656
+ if (/^\[thinking\]/i.test(normalized)) {
657
+ emitReasoning(normalized.replace(/^\[thinking\]\s*/i, ''));
658
+ inVerboseThinkingBlock = true;
659
+ return;
660
+ }
661
+ if (/^Args:\s*\{$/i.test(normalized)) {
662
+ inVerboseArgsBlock = true;
663
+ return;
664
+ }
665
+ if (/^Result:\s*/i.test(normalized)) {
666
+ if (!normalized.endsWith('}'))
667
+ inVerboseResultBlock = true;
668
+ return;
669
+ }
670
+ if (/^Query:\s*/i.test(normalized)) {
671
+ inEchoedQuery = true;
672
+ return;
673
+ }
674
+ if (inEchoedQuery && isHermesRuntimeBoundaryLine(normalized)) {
675
+ inEchoedQuery = false;
676
+ }
677
+ if (/^Agent runtime configuration:\s*$/i.test(normalized)) {
678
+ inEchoedQuery = false;
679
+ inRuntimeConfig = true;
680
+ return;
681
+ }
682
+ if (inEchoedQuery)
683
+ return;
684
+ if (inRuntimeConfig) {
685
+ if (/^User message:\s*$/i.test(normalized)) {
686
+ inRuntimeConfig = false;
687
+ inUserMessageEcho = true;
688
+ return;
689
+ }
690
+ if (isHermesRuntimeConfigLine(normalized))
691
+ return;
692
+ inRuntimeConfig = false;
693
+ }
694
+ if (isHermesNoiseLine(normalized))
695
+ return;
696
+ emitOutput(normalized);
697
+ },
698
+ };
699
+ }
700
+ function parseHermesToolUse(line) {
701
+ const match = line.match(/\bTool call:\s*([A-Za-z_][\w.-]*)\s+with args:\s*(.+)$/i);
702
+ if (!match)
703
+ return null;
704
+ const name = match[1];
705
+ const rawInput = match[2].replace(/\.\.\.$/, '').trim();
706
+ return {
707
+ name,
708
+ rawInput,
709
+ input: parseJsonObject(rawInput) ?? rawInput,
710
+ };
711
+ }
712
+ function parseHermesReasoningLine(line) {
713
+ return line.match(/\b(?:Reasoning|Thinking):\s*(.+)$/i)?.[1]?.trim();
714
+ }
715
+ function parseCapturedReasoningStart(line) {
716
+ const match = line.match(/\bCaptured reasoning \(\d+ chars\):\s*(.*)$/i);
717
+ if (!match)
718
+ return undefined;
719
+ return match[1].trim();
720
+ }
721
+ function isHermesLogLine(line) {
722
+ return /^\d{2}:\d{2}:\d{2}\s+-\s+/.test(line);
723
+ }
724
+ function parseHermesUsageLine(line) {
725
+ const apiCallMatch = line.match(/\bAPI call #\d+:.*?\bin=([\d,]+)\s+out=([\d,]+)\s+total=([\d,]+)/i);
726
+ if (apiCallMatch) {
727
+ const cache = line.match(/\bcache=([\d,]+)\/[\d,]+/i)?.[1];
728
+ return formatUsageLine({
729
+ input: apiCallMatch[1],
730
+ output: apiCallMatch[2],
731
+ total: apiCallMatch[3],
732
+ cached: cache,
733
+ });
734
+ }
735
+ const tokenUsageMatch = line.match(/\bToken usage:\s*prompt=([\d,]+),\s*completion=([\d,]+),\s*total=([\d,]+)/i);
736
+ if (tokenUsageMatch) {
737
+ return formatUsageLine({
738
+ input: tokenUsageMatch[1],
739
+ output: tokenUsageMatch[2],
740
+ total: tokenUsageMatch[3],
741
+ });
742
+ }
743
+ return undefined;
744
+ }
745
+ function formatUsageLine(usage) {
746
+ const parts = [
747
+ `[Usage] total: ${usage.total}`,
748
+ `input: ${usage.input}`,
749
+ `output: ${usage.output}`,
750
+ ];
751
+ if (usage.cached)
752
+ parts.push(`cached: ${usage.cached}`);
753
+ return parts.join(' ');
754
+ }
755
+ function parseJsonObject(value) {
756
+ if (!value.startsWith('{') && !value.startsWith('['))
757
+ return undefined;
758
+ try {
759
+ return JSON.parse(value);
760
+ }
761
+ catch {
762
+ return undefined;
763
+ }
764
+ }
765
+ function isHermesNoiseLine(line) {
766
+ return /^\u{1f916}\s*AI Agent initialized\b/iu.test(line)
767
+ || /^\u{1f517}\s*Using custom base URL:/iu.test(line)
768
+ || /^\u{1f511}\s*Using API key:/iu.test(line)
769
+ || /^\u{1f511}\s*Using token:/iu.test(line)
770
+ || isHermesRuntimeBoundaryLine(line)
771
+ || isHermesVerboseUiLine(line);
772
+ }
773
+ function isHermesRuntimeBoundaryLine(line) {
774
+ return /^Initializing agent\b/i.test(line)
775
+ || /^\u{1f916}\s*AI Agent initialized\b/iu.test(line);
776
+ }
777
+ function isHermesRuntimeConfigLine(line) {
778
+ return /^-\s+/.test(line)
779
+ || /^(skills|directory,|paths\.|tools you have,|are|built-in runtime tools|internals, previous sessions, or filesystem settings\.)$/i.test(line)
780
+ || /^For Bash commands that create or modify files under the current working\b/i.test(line)
781
+ || /^When asked what MCP servers, skills, runtime tools, or Agent Spaces channel\b/i.test(line)
782
+ || /^Important distinction: MCP servers configured for this agent are only the names\b/i.test(line)
783
+ || /^in "MCP servers configured for this agent"\./i.test(line)
784
+ || /^Do not infer availability from provider-side function names, hidden runtime\b/i.test(line);
785
+ }
786
+ function isHermesVerboseUiLine(line) {
787
+ return /^[\u{2705}\u{1f6e0}\u{fe0f}\u{26a0}\u{1f512}\u{1f4be}\u{1f4ca}\u{1f4ac}\u{1f389}\u{1f4de}]/u.test(line)
788
+ || /^[\u{2500}\u{256d}\u{2570}\u{2502}]/u.test(line)
789
+ || /^\[thinking\]/i.test(line)
790
+ || /^┊\s*/u.test(line)
791
+ || /^(Tool \d+:|Args:|Result:|Resume this session with:|Session:|Duration:|Messages:)/i.test(line)
792
+ || /^hermes\s+--resume\b/i.test(line)
793
+ || /^\{".*"\}?$/.test(line);
794
+ }
795
+ function isHermesVerboseBlockBoundaryLine(line) {
796
+ return /^\[thinking\]/i.test(line)
797
+ || /^[\u{2705}\u{1f389}\u{1f4de}]/u.test(line)
798
+ || /^[\u{2500}\u{256d}\u{2570}\u{2502}]/u.test(line)
799
+ || /^(Tool \d+:|Args:|Result:|Resume this session with:|Session:|Duration:|Messages:)/i.test(line)
800
+ || /^hermes\s+--resume\b/i.test(line);
801
+ }
802
+ function isHermesThinkingContinuationLine(line) {
803
+ return !isHermesVerboseBlockBoundaryLine(line)
804
+ && !/^\[Usage\]/i.test(line)
805
+ && !/^Tool:\s*/i.test(line);
806
+ }
807
+ function isDiagnosticOutputLine(line) {
808
+ return line.startsWith('[stderr]')
809
+ || /^\[Usage\]/i.test(line)
810
+ || /^Tool:\s*/i.test(line)
811
+ || isHermesNoiseLine(line);
812
+ }
210
813
  //# sourceMappingURL=hermes-runtime.js.map