@agent-spaces/server 0.3.65 → 0.3.67

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 (230) hide show
  1. package/dist/adapters/agent-runtime.js +4 -0
  2. package/dist/adapters/codex-function-tool-bridge.js +144 -0
  3. package/dist/adapters/codex-runtime.js +23 -4
  4. package/dist/adapters/git.js +12 -1
  5. package/dist/adapters/hermes-runtime.js +210 -0
  6. package/dist/agents/issue-task-controller.js +69 -2
  7. package/dist/agents/pull-request-agent.js +115 -0
  8. package/dist/agents/scheduler-agent.js +1 -1
  9. package/dist/app.js +6 -2
  10. package/dist/middleware/auth.js +1 -1
  11. package/dist/package.json +2 -1
  12. package/dist/routes/agent-sse.js +1 -0
  13. package/dist/routes/issue.js +1 -1
  14. package/dist/routes/notification.js +10 -0
  15. package/dist/routes/version.js +94 -0
  16. package/dist/routes/worktree.js +93 -0
  17. package/dist/services/agent.js +1 -1
  18. package/dist/services/notification-center.js +9 -0
  19. package/dist/services/version.js +59 -0
  20. package/dist/services/workflow-command-runner.js +23 -0
  21. package/dist/services/workflow.js +25 -8
  22. package/dist/services/workspace.js +23 -1
  23. package/dist/services/worktree.js +207 -0
  24. package/dist/storage/worktree-store.js +33 -0
  25. package/dist/web/404.html +1 -1
  26. package/dist/web/__next.__PAGE__.txt +4 -4
  27. package/dist/web/__next._full.txt +21 -21
  28. package/dist/web/__next._head.txt +4 -4
  29. package/dist/web/__next._index.txt +13 -13
  30. package/dist/web/__next._tree.txt +2 -2
  31. package/dist/web/_next/static/chunks/{08x85faoz58i7.js → 0-92u-tiuy40a.js} +4 -4
  32. package/dist/web/_next/static/chunks/{0db14450oqzhg.js → 0-yizd8osmas2.js} +1 -1
  33. package/dist/web/_next/static/chunks/0.ln~3mnv8.69.js +1 -0
  34. package/dist/web/_next/static/chunks/{11cchuwoupmbf.js → 01y3e9pemva7b.js} +10 -10
  35. package/dist/web/_next/static/chunks/02l~tmzrgny.~.js +1 -0
  36. package/dist/web/_next/static/chunks/03f1bwuynjdmk.js +1 -0
  37. package/dist/web/_next/static/chunks/04cb4kc9r2pd1.js +1 -0
  38. package/dist/web/_next/static/chunks/04de6ha5_e3pp.js +1 -0
  39. package/dist/web/_next/static/chunks/09he0p79ov1rn.js +1 -0
  40. package/dist/web/_next/static/chunks/09vpocs4zuc8h.js +1 -0
  41. package/dist/web/_next/static/chunks/0_o2exgr0e_nh.js +1 -0
  42. package/dist/web/_next/static/chunks/{0xbaakj0dfc0..js → 0_y9nskc-xt2k.js} +1 -1
  43. package/dist/web/_next/static/chunks/0clnj-1fi6u8j.js +3 -0
  44. package/dist/web/_next/static/chunks/0gv40by0zzxs_.js +1 -0
  45. package/dist/web/_next/static/chunks/0iusr8y88.kjg.js +1 -0
  46. package/dist/web/_next/static/chunks/{0-a.-hl3wv99k.js → 0j-8418fxvkd1.js} +1 -1
  47. package/dist/web/_next/static/chunks/0j.wxxyoclma5.js +1 -0
  48. package/dist/web/_next/static/chunks/{0.6b64sy0g5.3.js → 0jq.16sozqik..js} +1 -1
  49. package/dist/web/_next/static/chunks/0jw8xdk_fpnp0.css +1 -0
  50. package/dist/web/_next/static/chunks/0l1lkd-wcotma.js +1 -0
  51. package/dist/web/_next/static/chunks/{0u6h-hqc_i52e.js → 0ncxf_zr4n839.js} +1 -1
  52. package/dist/web/_next/static/chunks/0ndo7um~v7vco.js +1 -0
  53. package/dist/web/_next/static/chunks/0ngk.~kan8wq_.js +1 -0
  54. package/dist/web/_next/static/chunks/0ppr271hz0xoq.js +1 -0
  55. package/dist/web/_next/static/chunks/0rt9bofnn6qvn.js +1 -0
  56. package/dist/web/_next/static/chunks/0t64.9jd00hio.js +3 -0
  57. package/dist/web/_next/static/chunks/0taui9--5n6-5.js +1 -0
  58. package/dist/web/_next/static/chunks/0ty39169lh~z3.js +1 -0
  59. package/dist/web/_next/static/chunks/0v8nof~waedlu.js +1 -0
  60. package/dist/web/_next/static/chunks/{0-pi.67w.ymsr.js → 0vcfoagnpf.aw.js} +1 -1
  61. package/dist/web/_next/static/chunks/{0vis~lx0nxgn-.js → 0wwwq.gyyz0vg.js} +3 -3
  62. package/dist/web/_next/static/chunks/0x5lak8yz7pm7.js +1 -0
  63. package/dist/web/_next/static/chunks/0xon_.4-o0_zq.js +7 -0
  64. package/dist/web/_next/static/chunks/0zjnrbt7inime.js +1 -0
  65. package/dist/web/_next/static/chunks/0zwebpitvpuv1.js +1 -0
  66. package/dist/web/_next/static/chunks/0~02o4g-o6n~m.js +1 -0
  67. package/dist/web/_next/static/chunks/{0w7.tb29958zj.js → 0~fjezp.k4qk3.js} +1 -1
  68. package/dist/web/_next/static/chunks/0~sh93re1-_rp.js +1 -0
  69. package/dist/web/_next/static/chunks/10_niiq0k6o-p.js +5 -0
  70. package/dist/web/_next/static/chunks/12tjr0_2ccrtq.js +2 -0
  71. package/dist/web/_next/static/chunks/12x5.go_x9mfk.js +1 -0
  72. package/dist/web/_next/static/chunks/{0l2w0qz1ea7ql.js → 12~5dk9i9d16t.js} +2 -2
  73. package/dist/web/_next/static/chunks/13iye4rv5p31b.js +1 -0
  74. package/dist/web/_next/static/chunks/{01~quun6sefgl.js → 13~y-b.b9rrw4.js} +1 -1
  75. package/dist/web/_next/static/chunks/151wzhb5q0lo5.js +1 -0
  76. package/dist/web/_not-found/__next._full.txt +19 -19
  77. package/dist/web/_not-found/__next._head.txt +4 -4
  78. package/dist/web/_not-found/__next._index.txt +13 -13
  79. package/dist/web/_not-found/__next._not-found.__PAGE__.txt +2 -2
  80. package/dist/web/_not-found/__next._not-found.txt +3 -3
  81. package/dist/web/_not-found/__next._tree.txt +2 -2
  82. package/dist/web/_not-found.html +1 -1
  83. package/dist/web/_not-found.txt +19 -19
  84. package/dist/web/index.html +1 -1
  85. package/dist/web/index.txt +21 -21
  86. package/dist/web/login/__next._full.txt +21 -21
  87. package/dist/web/login/__next._head.txt +4 -4
  88. package/dist/web/login/__next._index.txt +13 -13
  89. package/dist/web/login/__next._tree.txt +2 -2
  90. package/dist/web/login/__next.login.__PAGE__.txt +4 -4
  91. package/dist/web/login/__next.login.txt +3 -3
  92. package/dist/web/login.html +1 -1
  93. package/dist/web/login.txt +21 -21
  94. package/dist/web/settings/__next._full.txt +23 -23
  95. package/dist/web/settings/__next._head.txt +4 -4
  96. package/dist/web/settings/__next._index.txt +13 -13
  97. package/dist/web/settings/__next._tree.txt +2 -2
  98. package/dist/web/settings/__next.settings.__PAGE__.txt +4 -4
  99. package/dist/web/settings/__next.settings.txt +5 -5
  100. package/dist/web/settings/agents/__next._full.txt +23 -23
  101. package/dist/web/settings/agents/__next._head.txt +4 -4
  102. package/dist/web/settings/agents/__next._index.txt +13 -13
  103. package/dist/web/settings/agents/__next._tree.txt +2 -2
  104. package/dist/web/settings/agents/__next.settings.agents.__PAGE__.txt +4 -4
  105. package/dist/web/settings/agents/__next.settings.agents.txt +3 -3
  106. package/dist/web/settings/agents/__next.settings.txt +5 -5
  107. package/dist/web/settings/agents.html +1 -1
  108. package/dist/web/settings/agents.txt +23 -23
  109. package/dist/web/settings/mcps/__next._full.txt +23 -23
  110. package/dist/web/settings/mcps/__next._head.txt +4 -4
  111. package/dist/web/settings/mcps/__next._index.txt +13 -13
  112. package/dist/web/settings/mcps/__next._tree.txt +2 -2
  113. package/dist/web/settings/mcps/__next.settings.mcps.__PAGE__.txt +4 -4
  114. package/dist/web/settings/mcps/__next.settings.mcps.txt +3 -3
  115. package/dist/web/settings/mcps/__next.settings.txt +5 -5
  116. package/dist/web/settings/mcps.html +1 -1
  117. package/dist/web/settings/mcps.txt +23 -23
  118. package/dist/web/settings/models/__next._full.txt +23 -23
  119. package/dist/web/settings/models/__next._head.txt +4 -4
  120. package/dist/web/settings/models/__next._index.txt +13 -13
  121. package/dist/web/settings/models/__next._tree.txt +2 -2
  122. package/dist/web/settings/models/__next.settings.models.__PAGE__.txt +4 -4
  123. package/dist/web/settings/models/__next.settings.models.txt +3 -3
  124. package/dist/web/settings/models/__next.settings.txt +5 -5
  125. package/dist/web/settings/models.html +1 -1
  126. package/dist/web/settings/models.txt +23 -23
  127. package/dist/web/settings/output-styles/__next._full.txt +23 -23
  128. package/dist/web/settings/output-styles/__next._head.txt +4 -4
  129. package/dist/web/settings/output-styles/__next._index.txt +13 -13
  130. package/dist/web/settings/output-styles/__next._tree.txt +2 -2
  131. package/dist/web/settings/output-styles/__next.settings.output-styles.__PAGE__.txt +4 -4
  132. package/dist/web/settings/output-styles/__next.settings.output-styles.txt +3 -3
  133. package/dist/web/settings/output-styles/__next.settings.txt +5 -5
  134. package/dist/web/settings/output-styles.html +1 -1
  135. package/dist/web/settings/output-styles.txt +23 -23
  136. package/dist/web/settings/prompts/__next._full.txt +23 -23
  137. package/dist/web/settings/prompts/__next._head.txt +4 -4
  138. package/dist/web/settings/prompts/__next._index.txt +13 -13
  139. package/dist/web/settings/prompts/__next._tree.txt +2 -2
  140. package/dist/web/settings/prompts/__next.settings.prompts.__PAGE__.txt +4 -4
  141. package/dist/web/settings/prompts/__next.settings.prompts.txt +3 -3
  142. package/dist/web/settings/prompts/__next.settings.txt +5 -5
  143. package/dist/web/settings/prompts.html +1 -1
  144. package/dist/web/settings/prompts.txt +23 -23
  145. package/dist/web/settings/providers/__next._full.txt +23 -23
  146. package/dist/web/settings/providers/__next._head.txt +4 -4
  147. package/dist/web/settings/providers/__next._index.txt +13 -13
  148. package/dist/web/settings/providers/__next._tree.txt +2 -2
  149. package/dist/web/settings/providers/__next.settings.providers.__PAGE__.txt +4 -4
  150. package/dist/web/settings/providers/__next.settings.providers.txt +3 -3
  151. package/dist/web/settings/providers/__next.settings.txt +5 -5
  152. package/dist/web/settings/providers.html +1 -1
  153. package/dist/web/settings/providers.txt +23 -23
  154. package/dist/web/settings/skills/__next._full.txt +23 -23
  155. package/dist/web/settings/skills/__next._head.txt +4 -4
  156. package/dist/web/settings/skills/__next._index.txt +13 -13
  157. package/dist/web/settings/skills/__next._tree.txt +2 -2
  158. package/dist/web/settings/skills/__next.settings.skills.__PAGE__.txt +4 -4
  159. package/dist/web/settings/skills/__next.settings.skills.txt +3 -3
  160. package/dist/web/settings/skills/__next.settings.txt +5 -5
  161. package/dist/web/settings/skills.html +1 -1
  162. package/dist/web/settings/skills.txt +23 -23
  163. package/dist/web/settings/tools/__next._full.txt +23 -23
  164. package/dist/web/settings/tools/__next._head.txt +4 -4
  165. package/dist/web/settings/tools/__next._index.txt +13 -13
  166. package/dist/web/settings/tools/__next._tree.txt +2 -2
  167. package/dist/web/settings/tools/__next.settings.tools.__PAGE__.txt +4 -4
  168. package/dist/web/settings/tools/__next.settings.tools.txt +3 -3
  169. package/dist/web/settings/tools/__next.settings.txt +5 -5
  170. package/dist/web/settings/tools.html +1 -1
  171. package/dist/web/settings/tools.txt +23 -23
  172. package/dist/web/settings.html +1 -1
  173. package/dist/web/settings.txt +23 -23
  174. package/dist/web/workflows/__next._full.txt +22 -22
  175. package/dist/web/workflows/__next._head.txt +4 -4
  176. package/dist/web/workflows/__next._index.txt +13 -13
  177. package/dist/web/workflows/__next._tree.txt +2 -2
  178. package/dist/web/workflows/__next.workflows.__PAGE__.txt +4 -4
  179. package/dist/web/workflows/__next.workflows.txt +3 -3
  180. package/dist/web/workflows.html +1 -1
  181. package/dist/web/workflows.txt +22 -22
  182. package/dist/web/workspace/_/__next._full.txt +20 -20
  183. package/dist/web/workspace/_/__next._head.txt +4 -4
  184. package/dist/web/workspace/_/__next._index.txt +13 -13
  185. package/dist/web/workspace/_/__next._tree.txt +2 -2
  186. package/dist/web/workspace/_/__next.workspace.$d$id.__PAGE__.txt +3 -3
  187. package/dist/web/workspace/_/__next.workspace.$d$id.txt +3 -3
  188. package/dist/web/workspace/_/__next.workspace.txt +3 -3
  189. package/dist/web/workspace/_.html +1 -1
  190. package/dist/web/workspace/_.txt +20 -20
  191. package/dist/web/workspaces/__next._full.txt +21 -21
  192. package/dist/web/workspaces/__next._head.txt +4 -4
  193. package/dist/web/workspaces/__next._index.txt +13 -13
  194. package/dist/web/workspaces/__next._tree.txt +2 -2
  195. package/dist/web/workspaces/__next.workspaces.__PAGE__.txt +4 -4
  196. package/dist/web/workspaces/__next.workspaces.txt +3 -3
  197. package/dist/web/workspaces.html +1 -1
  198. package/dist/web/workspaces.txt +21 -21
  199. package/dist/ws/agent-prompt.js +32 -3
  200. package/dist/ws/agent-runner.js +1 -0
  201. package/package.json +2 -1
  202. package/dist/web/_next/static/chunks/0-v_p6j5u3_9l.js +0 -1
  203. package/dist/web/_next/static/chunks/0.-x29t-8gk0r.js +0 -1
  204. package/dist/web/_next/static/chunks/0.l5e8~r7_0~p.js +0 -1
  205. package/dist/web/_next/static/chunks/0.n~yfwf4~6w-.js +0 -1
  206. package/dist/web/_next/static/chunks/00-syqzi-6zxr.js +0 -1
  207. package/dist/web/_next/static/chunks/02j4sr9psucf-.js +0 -1
  208. package/dist/web/_next/static/chunks/06si8l0doqe8p.js +0 -1
  209. package/dist/web/_next/static/chunks/08kfx8on9oty~.js +0 -1
  210. package/dist/web/_next/static/chunks/08ugbvzr20drp.js +0 -2
  211. package/dist/web/_next/static/chunks/0_cqojbge48kq.js +0 -5
  212. package/dist/web/_next/static/chunks/0d6~~lv0-6kex.js +0 -3
  213. package/dist/web/_next/static/chunks/0gdwop9cnpmtu.js +0 -1
  214. package/dist/web/_next/static/chunks/0ghx3j_.-gdyd.js +0 -1
  215. package/dist/web/_next/static/chunks/0kbvoxla7f751.js +0 -1
  216. package/dist/web/_next/static/chunks/0paaat8y96ayc.js +0 -7
  217. package/dist/web/_next/static/chunks/0pq318f8xgafg.js +0 -1
  218. package/dist/web/_next/static/chunks/0t6s8of0qr6nc.js +0 -1
  219. package/dist/web/_next/static/chunks/0xt-8wk21gs7g.js +0 -1
  220. package/dist/web/_next/static/chunks/0yd.m1afqg.2t.js +0 -3
  221. package/dist/web/_next/static/chunks/0yhbr8hl09b-5.js +0 -1
  222. package/dist/web/_next/static/chunks/0z~vqzbm_q371.js +0 -1
  223. package/dist/web/_next/static/chunks/0~~_ltu6dx_ch.js +0 -1
  224. package/dist/web/_next/static/chunks/12oxcgyhpaxw~.js +0 -1
  225. package/dist/web/_next/static/chunks/1452eibz62gs6.css +0 -1
  226. package/dist/web/_next/static/chunks/16zlreiems5cn.js +0 -1
  227. package/dist/web/_next/static/chunks/18awqjk9-o5m0.js +0 -1
  228. /package/dist/web/_next/static/{x4-CsKe6qUQyFDWVtfvUg → rGXyAK8rgod1NjF3bLZAY}/_buildManifest.js +0 -0
  229. /package/dist/web/_next/static/{x4-CsKe6qUQyFDWVtfvUg → rGXyAK8rgod1NjF3bLZAY}/_clientMiddlewareManifest.js +0 -0
  230. /package/dist/web/_next/static/{x4-CsKe6qUQyFDWVtfvUg → rGXyAK8rgod1NjF3bLZAY}/_ssgManifest.js +0 -0
@@ -3,10 +3,12 @@
3
3
  */
4
4
  import { ClaudeCodeRuntime } from './claude-code-runtime/index.js';
5
5
  import { CodexRuntime } from './codex-runtime.js';
6
+ import { HermesRuntime } from './hermes-runtime.js';
6
7
  import { LangChainRuntime } from './langchain-runtime.js';
7
8
  import { OpenAgentSdkRuntime } from './open-agent-sdk-runtime.js';
8
9
  export { ClaudeCodeRuntime } from './claude-code-runtime/index.js';
9
10
  export { CodexRuntime } from './codex-runtime.js';
11
+ export { HermesRuntime } from './hermes-runtime.js';
10
12
  export { LangChainRuntime } from './langchain-runtime.js';
11
13
  export { OpenAgentSdkRuntime } from './open-agent-sdk-runtime.js';
12
14
  export function createAgentRuntime(configOrProvider = {}, model) {
@@ -22,6 +24,8 @@ export function createAgentRuntime(configOrProvider = {}, model) {
22
24
  return new CodexRuntime(config);
23
25
  case 'langchain':
24
26
  return new LangChainRuntime(config);
27
+ case 'hermes':
28
+ return new HermesRuntime(config);
25
29
  }
26
30
  }
27
31
  //# sourceMappingURL=agent-runtime.js.map
@@ -0,0 +1,144 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { createServer } from 'node:http';
3
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
4
+ import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
5
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
6
+ const AGENT_SPACES_MCP_SERVER_NAME = 'agent-spaces';
7
+ export async function startCodexFunctionToolBridge(functionTools, log) {
8
+ if (!functionTools?.length)
9
+ return undefined;
10
+ const toolsByName = new Map(functionTools.map((functionTool) => [functionTool.name, functionTool]));
11
+ const mcpServer = new Server({ name: AGENT_SPACES_MCP_SERVER_NAME, version: '0.1.0' }, {
12
+ capabilities: {
13
+ tools: { listChanged: false },
14
+ },
15
+ instructions: 'Agent Spaces built-in runtime tools for workspace data.',
16
+ });
17
+ const transport = new StreamableHTTPServerTransport({
18
+ sessionIdGenerator: randomUUID,
19
+ enableJsonResponse: true,
20
+ });
21
+ mcpServer.setRequestHandler(ListToolsRequestSchema, () => ({
22
+ tools: functionTools.map(toMcpTool),
23
+ }));
24
+ mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => {
25
+ const functionTool = toolsByName.get(request.params.name);
26
+ if (!functionTool) {
27
+ throw new McpError(ErrorCode.InvalidParams, `Tool ${request.params.name} not found`);
28
+ }
29
+ const result = await functionTool.execute(request.params.arguments ?? {});
30
+ return {
31
+ content: [
32
+ {
33
+ type: 'text',
34
+ text: stringifyToolResult(result),
35
+ },
36
+ ],
37
+ ...(isRecord(result) ? { structuredContent: result } : {}),
38
+ };
39
+ });
40
+ await mcpServer.connect(transport);
41
+ const httpServer = createServer(async (req, res) => {
42
+ try {
43
+ const url = new URL(req.url ?? '/', 'http://127.0.0.1');
44
+ if (url.pathname !== '/mcp') {
45
+ res.writeHead(404).end('Not found');
46
+ return;
47
+ }
48
+ await transport.handleRequest(req, res);
49
+ }
50
+ catch (err) {
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ log?.(`function tool bridge request failed | ${message}`);
53
+ if (!res.headersSent)
54
+ res.writeHead(500, { 'content-type': 'text/plain; charset=utf-8' });
55
+ if (!res.writableEnded)
56
+ res.end(message);
57
+ }
58
+ });
59
+ await listen(httpServer);
60
+ const address = httpServer.address();
61
+ if (!address || typeof address === 'string') {
62
+ await closeHttpServer(httpServer);
63
+ await mcpServer.close();
64
+ throw new Error('Failed to start Codex function tool bridge');
65
+ }
66
+ const url = `http://127.0.0.1:${address.port}/mcp`;
67
+ log?.(`function tool bridge started | url=${url} tools=${functionTools.map((tool) => tool.name).join(',')}`);
68
+ return {
69
+ name: AGENT_SPACES_MCP_SERVER_NAME,
70
+ url,
71
+ close: async () => {
72
+ await mcpServer.close();
73
+ await closeHttpServer(httpServer);
74
+ log?.('function tool bridge stopped');
75
+ },
76
+ };
77
+ }
78
+ function toMcpTool(functionTool) {
79
+ return {
80
+ name: functionTool.name,
81
+ description: functionTool.description,
82
+ inputSchema: normalizeToolInputSchema(functionTool.inputSchema),
83
+ annotations: toMcpToolAnnotations(functionTool),
84
+ };
85
+ }
86
+ function normalizeToolInputSchema(schema) {
87
+ if (schema.type === 'object')
88
+ return schema;
89
+ return {
90
+ type: 'object',
91
+ properties: normalizeSchemaProperties(schema.properties),
92
+ required: Array.isArray(schema.required) ? schema.required.map(String) : [],
93
+ };
94
+ }
95
+ function toMcpToolAnnotations(functionTool) {
96
+ if (!functionTool.annotations)
97
+ return undefined;
98
+ return {
99
+ readOnlyHint: functionTool.annotations.readOnly,
100
+ destructiveHint: functionTool.annotations.destructive,
101
+ openWorldHint: functionTool.annotations.openWorld,
102
+ };
103
+ }
104
+ function stringifyToolResult(result) {
105
+ if (result === undefined)
106
+ return 'null';
107
+ return JSON.stringify(result, null, 2) ?? String(result);
108
+ }
109
+ function normalizeSchemaProperties(properties) {
110
+ if (!isRecord(properties))
111
+ return {};
112
+ return Object.fromEntries(Object.entries(properties).filter((entry) => isRecord(entry[1])));
113
+ }
114
+ function listen(server) {
115
+ return new Promise((resolve, reject) => {
116
+ const onError = (err) => {
117
+ server.off('listening', onListening);
118
+ reject(err);
119
+ };
120
+ const onListening = () => {
121
+ server.off('error', onError);
122
+ resolve();
123
+ };
124
+ server.once('error', onError);
125
+ server.once('listening', onListening);
126
+ server.listen(0, '127.0.0.1');
127
+ });
128
+ }
129
+ function closeHttpServer(server) {
130
+ if (!server.listening)
131
+ return Promise.resolve();
132
+ return new Promise((resolve, reject) => {
133
+ server.close((err) => {
134
+ if (err)
135
+ reject(err);
136
+ else
137
+ resolve();
138
+ });
139
+ });
140
+ }
141
+ function isRecord(value) {
142
+ return Boolean(value && typeof value === 'object' && !Array.isArray(value));
143
+ }
144
+ //# sourceMappingURL=codex-function-tool-bridge.js.map
@@ -2,6 +2,7 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, rmSync, statSync, wri
2
2
  import { basename, extname, join } from 'node:path';
3
3
  import { Codex } from '@openai/codex-sdk';
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 OpenAI's Codex SDK.
7
8
  * Spawns the bundled Codex CLI and consumes its structured JSONL event stream.
@@ -23,12 +24,15 @@ export class CodexRuntime {
23
24
  if (codexHome)
24
25
  prepareCodexHome(codexHome, agentDir);
25
26
  const skillNames = normalizeSkillNames(options?.skills, codexHome);
26
- const configOverrides = buildCodexConfig(this.config, options, skillNames);
27
27
  const sandboxMode = normalizeSandboxMode(this.config.permissionMode);
28
28
  const approvalPolicy = normalizeApprovalPolicy(this.config.permissionMode);
29
- d(`starting | cwd=${cwd} model=${this.config.model ?? 'default'} sandboxMode=${sandboxMode} approvalPolicy=${approvalPolicy} maxTurns=${options?.maxTurns ?? '∞'} mcpServers=${Object.keys(options?.mcpServers ?? {}).join(',') || '-'} skills=${skillNames.join(',') || '-'} codexHome=${codexHome ?? 'default'} sandboxDirs=${options?.sandboxDirs?.join(',') ?? '-'}`);
29
+ let functionToolBridge;
30
+ d(`starting | cwd=${cwd} model=${this.config.model ?? 'default'} sandboxMode=${sandboxMode} approvalPolicy=${approvalPolicy} maxTurns=${options?.maxTurns ?? '∞'} mcpServers=${Object.keys(options?.mcpServers ?? {}).join(',') || '-'} functionTools=${options?.functionTools?.map((tool) => tool.name).join(',') || '-'} skills=${skillNames.join(',') || '-'} codexHome=${codexHome ?? 'default'} sandboxDirs=${options?.sandboxDirs?.join(',') ?? '-'}`);
30
31
  d(`prompt: ${prompt.slice(0, 300)}${prompt.length > 300 ? '...' : ''}`);
31
32
  try {
33
+ functionToolBridge = await startCodexFunctionToolBridge(options?.functionTools, d);
34
+ const mcpServers = withFunctionToolBridge(options?.mcpServers, functionToolBridge);
35
+ const configOverrides = buildCodexConfig(this.config, mcpServers, skillNames);
32
36
  const codex = new Codex({
33
37
  apiKey: this.config.apiKey,
34
38
  baseUrl: shouldUseCodexOpenAIBaseUrl(this.config) ? this.config.baseURL : undefined,
@@ -127,6 +131,13 @@ export class CodexRuntime {
127
131
  return { success: false, summary: 'Codex execution failed', artifacts: [], error: message, output, sessionId: options?.resumeSessionId };
128
132
  }
129
133
  finally {
134
+ try {
135
+ await functionToolBridge?.close();
136
+ }
137
+ catch (err) {
138
+ const message = err instanceof Error ? err.message : String(err);
139
+ d(`function tool bridge close failed | ${message}`);
140
+ }
130
141
  this.abortController = null;
131
142
  }
132
143
  }
@@ -143,13 +154,13 @@ function buildEnv(config, codexHome) {
143
154
  CODEX_INTERNAL_ORIGINATOR_OVERRIDE: process.env.CODEX_INTERNAL_ORIGINATOR_OVERRIDE || 'agent-spaces/server',
144
155
  });
145
156
  }
146
- function buildCodexConfig(runtimeConfig, options, skillNames) {
157
+ function buildCodexConfig(runtimeConfig, rawMcpServers, skillNames) {
147
158
  const config = {};
148
159
  Object.assign(config, buildCodexProviderConfig(runtimeConfig));
149
160
  if (runtimeConfig.thinkingEnabled !== false) {
150
161
  config.model_reasoning_effort = runtimeConfig.thinkingEffort ?? 'medium';
151
162
  }
152
- const mcpServers = normalizeMcpServers(options?.mcpServers);
163
+ const mcpServers = normalizeMcpServers(rawMcpServers);
153
164
  if (mcpServers)
154
165
  config.mcp_servers = mcpServers;
155
166
  if (skillNames.length > 0) {
@@ -157,6 +168,14 @@ function buildCodexConfig(runtimeConfig, options, skillNames) {
157
168
  }
158
169
  return removeUndefined(config);
159
170
  }
171
+ function withFunctionToolBridge(mcpServers, bridge) {
172
+ if (!bridge)
173
+ return mcpServers;
174
+ return {
175
+ ...(mcpServers ?? {}),
176
+ [bridge.name]: { url: bridge.url },
177
+ };
178
+ }
160
179
  function buildCodexProviderConfig(runtimeConfig) {
161
180
  if (!runtimeConfig.baseURL || shouldUseCodexOpenAIBaseUrl(runtimeConfig))
162
181
  return {};
@@ -170,6 +170,12 @@ export async function gitStatus(workspaceId) {
170
170
  try {
171
171
  const upstream = await getUpstreamRef(git, result.branch);
172
172
  if (upstream) {
173
+ const counts = await git.raw(['rev-list', '--left-right', '--count', `HEAD...${upstream}`]);
174
+ const [ahead, behind] = counts.trim().split(/\s+/).map((value) => Number(value));
175
+ if (Number.isFinite(ahead))
176
+ result.ahead = ahead;
177
+ if (Number.isFinite(behind))
178
+ result.behind = behind;
173
179
  const remoteHeadHash = await git.revparse(['--short', upstream]);
174
180
  result.remoteHeadHash = remoteHeadHash.trim();
175
181
  }
@@ -376,7 +382,7 @@ export async function gitPush(workspaceId) {
376
382
  const remotes = await git.getRemotes(true);
377
383
  if (!remotes.length)
378
384
  throw new Error('No remote repository configured. Please add a remote first.');
379
- await git.push('origin', branch);
385
+ await git.push('origin', branch, ['--set-upstream']);
380
386
  }
381
387
  export async function gitFetch(workspaceId) {
382
388
  const ws = getWorkspace(workspaceId);
@@ -392,6 +398,11 @@ export async function gitPull(workspaceId) {
392
398
  const git = getGit(ws);
393
399
  const status = await git.status();
394
400
  const branch = status.current || 'HEAD';
401
+ const upstream = await getUpstreamRef(git, branch);
402
+ if (upstream?.startsWith('origin/')) {
403
+ await git.pull('origin', upstream.slice('origin/'.length));
404
+ return;
405
+ }
395
406
  await git.pull('origin', branch);
396
407
  }
397
408
  export async function gitGetRemotes(workspaceId) {
@@ -0,0 +1,210 @@
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';
4
+ import { appendOutputStyleToPrompt, summarizeResult } from './agent-runtime-types.js';
5
+ /**
6
+ * Runtime backed by the external Hermes CLI.
7
+ *
8
+ * 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
+ */
11
+ export class HermesRuntime {
12
+ config;
13
+ child = null;
14
+ constructor(config = {}) {
15
+ this.config = config;
16
+ }
17
+ async execute(prompt, workingDir, options) {
18
+ const output = [];
19
+ const cwd = workingDir || process.cwd();
20
+ const startTime = Date.now();
21
+ const d = (message) => console.log(`[hermes] ${message}`);
22
+ const agentDir = options?.configDir;
23
+ 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)
36
+ 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,
46
+ });
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,
58
+ });
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 });
75
+ });
76
+ });
77
+ this.child.stderr.on('data', (chunk) => {
78
+ stderrBuffer = consumeLines(stderrBuffer + chunk, (line) => {
79
+ if (!line)
80
+ 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`);
107
+ finish({
108
+ success: true,
109
+ summary: summarizeResult(text),
110
+ artifacts: [],
111
+ output,
112
+ sessionId,
113
+ });
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
+ });
128
+ });
129
+ });
130
+ }
131
+ stop() {
132
+ this.child?.kill();
133
+ }
134
+ }
135
+ function buildHermesArgs(prompt, config, options) {
136
+ const args = ['chat', '-q', prompt, '--verbose'];
137
+ for (const skill of normalizeSkillNames(options?.skills))
138
+ args.push('-s', skill);
139
+ if (config.model)
140
+ args.push('--model', config.model);
141
+ if (config.provider)
142
+ args.push('--provider', String(config.provider));
143
+ return args;
144
+ }
145
+ function buildEnv(config, hermesHome) {
146
+ const apiKey = config.apiKey || process.env.HERMES_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY;
147
+ return {
148
+ ...process.env,
149
+ HERMES_HOME: hermesHome || process.env.HERMES_HOME,
150
+ HERMES_API_KEY: apiKey,
151
+ HERMES_BASE_URL: config.baseURL || process.env.HERMES_BASE_URL,
152
+ OPENAI_API_KEY: config.apiKey || process.env.OPENAI_API_KEY,
153
+ OPENAI_BASE_URL: config.baseURL || process.env.OPENAI_BASE_URL,
154
+ ANTHROPIC_API_KEY: config.apiKey || process.env.ANTHROPIC_API_KEY,
155
+ NO_COLOR: process.env.NO_COLOR || '1',
156
+ };
157
+ }
158
+ function prepareHermesHome(hermesHome, agentDir) {
159
+ mkdirSync(hermesHome, { recursive: true });
160
+ const skillsDir = join(hermesHome, 'skills');
161
+ rmSync(skillsDir, { recursive: true, force: true });
162
+ mkdirSync(skillsDir, { recursive: true });
163
+ if (!agentDir)
164
+ return;
165
+ const sourceSkillsDir = join(agentDir, 'skills');
166
+ if (!existsSync(sourceSkillsDir))
167
+ return;
168
+ for (const file of readdirSync(sourceSkillsDir)) {
169
+ if (extname(file).toLowerCase() !== '.md')
170
+ continue;
171
+ const sourceFile = join(sourceSkillsDir, file);
172
+ if (!statSync(sourceFile).isFile())
173
+ continue;
174
+ copyFileSync(sourceFile, join(skillsDir, basename(file)));
175
+ }
176
+ }
177
+ function consumeLines(buffer, onLine) {
178
+ const lines = buffer.split(/\r?\n/);
179
+ const remainder = lines.pop() ?? '';
180
+ for (const line of lines)
181
+ onLine(stripAnsi(line).trimEnd());
182
+ return remainder;
183
+ }
184
+ function flushLine(buffer, output, options, prefix = '') {
185
+ 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
+ }
191
+ return '';
192
+ }
193
+ function extractSessionId(line) {
194
+ const match = line.match(/\bsession(?:\s+id)?\s*[:=]\s*([a-zA-Z0-9._:-]+)/i);
195
+ return match?.[1];
196
+ }
197
+ function normalizeSkillNames(skills) {
198
+ if (!Array.isArray(skills))
199
+ return [];
200
+ return skills
201
+ .map((skill) => basename(skill).replace(/\.md$/i, '').trim())
202
+ .filter(Boolean);
203
+ }
204
+ function lastMeaningfulLine(output) {
205
+ return [...output].reverse().find((line) => line.trim() && !line.startsWith('[stderr]')) ?? '';
206
+ }
207
+ function stripAnsi(value) {
208
+ return value.replace(/\u001b\[[0-9;]*m/g, '');
209
+ }
210
+ //# sourceMappingURL=hermes-runtime.js.map
@@ -4,12 +4,13 @@ import * as channelService from '../services/channel.js';
4
4
  import * as issueService from '../services/issue.js';
5
5
  import * as taskService from '../services/task.js';
6
6
  import * as workspaceService from '../services/workspace.js';
7
- import { mapWorkflowToTaskDrafts, validateWorkflowForRun } from '../services/workflow.js';
7
+ import { getWorkflow, mapWorkflowToTaskDrafts, validateWorkflowForRun } from '../services/workflow.js';
8
8
  import { createIssueFunctionTools } from '../services/builtin-tools/index.js';
9
9
  import { getThinkingRuntimeConfig } from '../services/llm-model-config.js';
10
10
  import { prependPersistentAgentContext } from '../services/persistent-agent-context.js';
11
11
  import { completeIssueAgentProgress, createIssueAgentProgress, createIssueAgentProgressTracker } from './issue-agent-progress.js';
12
12
  import { wrapOnEventWithHooks } from '../services/hook-engine.js';
13
+ import { executeCommandNode } from '../services/workflow-command-runner.js';
13
14
  const ACTIVE_TASK_STATUSES = ['running', 'reviewing', 'retrying', 'waiting_review'];
14
15
  const activeIssueRuntimes = new Map();
15
16
  export async function syncIssueTasksAfterPlanning(workspaceId, issueId, input, ctx) {
@@ -137,6 +138,13 @@ export async function runIssueTask(workspaceId, issueId, taskId, ctx) {
137
138
  if (task.status !== 'pending') {
138
139
  return;
139
140
  }
141
+ // Check if this task came from a command workflow node
142
+ const workflow = findWorkflowForIssue(workspaceId, issue);
143
+ const commandNode = findCommandNodeForTask(workflow, task);
144
+ if (commandNode) {
145
+ await runCommandTask(workspaceId, issueId, taskId, commandNode, ctx);
146
+ return;
147
+ }
140
148
  const taskAgentPreset = findAgentForTask(workspaceId, issue, task);
141
149
  if (!taskAgentPreset) {
142
150
  if (issueService.getById(workspaceId, issueId)?.status === 'error')
@@ -274,6 +282,48 @@ export async function runIssueTask(workspaceId, issueId, taskId, ctx) {
274
282
  await scheduleRunnableIssueTasks(workspaceId, issueId, ctx);
275
283
  }
276
284
  }
285
+ async function runCommandTask(workspaceId, issueId, taskId, commandNode, ctx) {
286
+ const task = taskService.getById(workspaceId, taskId);
287
+ if (!task || task.status !== 'pending')
288
+ return;
289
+ const runningTask = taskService.updateStatus(workspaceId, taskId, 'running');
290
+ if (!runningTask)
291
+ return;
292
+ broadcastTaskUpdate(ctx, runningTask, 'pending');
293
+ ctx.broadcast('task.output', { taskId, data: `$ ${commandNode.data.script.split('\n')[0]}${commandNode.data.script.includes('\n') ? ' ...' : ''}` });
294
+ const result = await executeCommandNode(workspaceId, commandNode);
295
+ ctx.broadcast('task.output', { taskId, data: result.stdout });
296
+ if (result.stderr) {
297
+ ctx.broadcast('task.output', { taskId, data: result.stderr });
298
+ }
299
+ if (!result.success) {
300
+ const failedTask = taskService.updateStatus(workspaceId, taskId, 'failed', {
301
+ result: {
302
+ success: false,
303
+ summary: `Command failed with exit code ${result.exitCode}`,
304
+ artifacts: [],
305
+ error: result.stderr || `Exit code: ${result.exitCode}`,
306
+ },
307
+ });
308
+ broadcastTaskUpdate(ctx, failedTask, 'running');
309
+ await handleTaskFailure(workspaceId, issueId, taskId, {
310
+ success: false,
311
+ summary: `Command failed with exit code ${result.exitCode}`,
312
+ artifacts: [],
313
+ error: result.stderr || `Exit code: ${result.exitCode}`,
314
+ }, ctx);
315
+ return;
316
+ }
317
+ const completedTask = taskService.complete(workspaceId, taskId, {
318
+ success: true,
319
+ summary: result.stdout.slice(-500) || 'Command completed',
320
+ artifacts: [],
321
+ });
322
+ broadcastTaskUpdate(ctx, completedTask, 'running');
323
+ if (issueService.getById(workspaceId, issueId)?.continuousRun !== false) {
324
+ await scheduleRunnableIssueTasks(workspaceId, issueId, ctx);
325
+ }
326
+ }
277
327
  export async function continueIssueTaskScheduling(workspaceId, issueId, ctx) {
278
328
  await scheduleRunnableIssueTasks(workspaceId, issueId, ctx, { force: true });
279
329
  }
@@ -402,7 +452,7 @@ export function createTasksFromWorkflow(workspaceId, issueId, template, ctx) {
402
452
  });
403
453
  }
404
454
  function ensureWorkflowAgentsForRun(workspaceId, issue, template, ctx) {
405
- const workflowAgentIds = [...new Set(template.nodes.map((node) => node.data.agentConfigId).filter(Boolean))];
455
+ const workflowAgentIds = [...new Set(template.nodes.filter((n) => n.type === 'agent').map((node) => node.data.agentConfigId).filter(Boolean))];
406
456
  const mergedMembers = [...new Set([...(issue.members ?? []), ...workflowAgentIds])];
407
457
  const membersChanged = mergedMembers.length !== (issue.members ?? []).length
408
458
  || mergedMembers.some((member, index) => member !== issue.members?.[index]);
@@ -502,6 +552,23 @@ function unregisterActiveIssueRuntime(workspaceId, issueId, agentId) {
502
552
  function issueRunKey(workspaceId, issueId) {
503
553
  return `${workspaceId}:${issueId}`;
504
554
  }
555
+ function findWorkflowForIssue(workspaceId, issue) {
556
+ if (!issue.workflowId)
557
+ return null;
558
+ return getWorkflow(issue.workflowId);
559
+ }
560
+ function findCommandNodeForTask(workflow, task) {
561
+ if (!workflow)
562
+ return null;
563
+ if (task.agentConfigId)
564
+ return null;
565
+ for (const node of workflow.nodes) {
566
+ if (node.type === 'command' && (task.title === node.data.label || task.description?.startsWith('Command:'))) {
567
+ return node;
568
+ }
569
+ }
570
+ return null;
571
+ }
505
572
  function broadcastTaskUpdate(ctx, task, from) {
506
573
  if (!task)
507
574
  return;