@cursor/july 0.1.40 → 0.1.41

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 (239) hide show
  1. package/AGENTS.md +4 -3
  2. package/dist/bin/agent-serve.js +28 -9
  3. package/dist/channels/slack/attachments.d.ts +82 -0
  4. package/dist/channels/slack/attachments.d.ts.map +1 -0
  5. package/dist/channels/slack/attachments.js +520 -0
  6. package/dist/channels/slack/cursor-account.d.ts +7 -1
  7. package/dist/channels/slack/cursor-account.d.ts.map +1 -1
  8. package/dist/channels/slack/cursor-account.js +2 -2
  9. package/dist/channels/slack/dispatch.d.ts +4 -3
  10. package/dist/channels/slack/dispatch.d.ts.map +1 -1
  11. package/dist/channels/slack/dispatch.js +84 -21
  12. package/dist/channels/slack/inbound.d.ts +6 -0
  13. package/dist/channels/slack/inbound.d.ts.map +1 -1
  14. package/dist/channels/slack/inbound.js +48 -2
  15. package/dist/channels/slack/index.d.ts +1 -0
  16. package/dist/channels/slack/index.d.ts.map +1 -1
  17. package/dist/channels/slack/index.js +1 -0
  18. package/dist/channels/slack/manifest.d.ts.map +1 -1
  19. package/dist/channels/slack/manifest.js +1 -0
  20. package/dist/channels/slack/slack-channel.d.ts.map +1 -1
  21. package/dist/channels/slack/slack-channel.js +7 -4
  22. package/dist/channels/slack/types.d.ts +49 -1
  23. package/dist/channels/slack/types.d.ts.map +1 -1
  24. package/dist/docs/404.html +2 -2
  25. package/dist/docs/ab.html +6 -6
  26. package/dist/docs/assets/{ab.md.6cLOW7--.js → ab.md.hdxsvX5M.js} +2 -2
  27. package/dist/docs/assets/{app.DpGLSiym.js → app.mIw24pb1.js} +1 -1
  28. package/dist/docs/assets/{building-with-agents.md.txrcGU2B.js → building-with-agents.md.PeZaZA1P.js} +1 -1
  29. package/dist/docs/assets/chunks/@localSearchIndexroot.BA7V8dEJ.js +1 -0
  30. package/dist/docs/assets/chunks/{VPLocalSearchBox.B11XEMST.js → VPLocalSearchBox.hOzpPwrG.js} +1 -1
  31. package/dist/docs/assets/chunks/{theme.iTidHsAx.js → theme.BtFhUYmh.js} +2 -2
  32. package/dist/docs/assets/{deployment.md.CuK5SNjN.js → deployment.md.BHVjVqRa.js} +4 -4
  33. package/dist/docs/assets/{deployment.md.CuK5SNjN.lean.js → deployment.md.BHVjVqRa.lean.js} +1 -1
  34. package/dist/docs/assets/{evals.md.BQXI3rXy.js → evals.md.qeoGgPw0.js} +3 -3
  35. package/dist/docs/assets/example-agents_index.md.xmJ87d_3.js +2 -0
  36. package/dist/docs/assets/{guides_webhooks.md.BERuBSJW.js → guides_webhooks.md.DB-r_er9.js} +47 -4
  37. package/dist/docs/assets/{guides_webhooks.md.BERuBSJW.lean.js → guides_webhooks.md.DB-r_er9.lean.js} +1 -1
  38. package/dist/docs/assets/index.md.BKpbkCa6.js +24 -0
  39. package/dist/docs/assets/{index.md.CmhptOmN.lean.js → index.md.BKpbkCa6.lean.js} +1 -1
  40. package/dist/docs/assets/{quickstart.md.C_b6ESpD.js → quickstart.md.rIbfahJM.js} +4 -4
  41. package/dist/docs/assets/{reference_cli.md.Byvrg8eu.js → reference_cli.md.DqEqQpGn.js} +5 -5
  42. package/dist/docs/assets/{reference_cli.md.Byvrg8eu.lean.js → reference_cli.md.DqEqQpGn.lean.js} +1 -1
  43. package/dist/docs/assets/{reference_hooks.md.BGDw4VLm.js → reference_hooks.md.BHeG-vjK.js} +2 -2
  44. package/dist/docs/assets/{reference_hooks.md.BGDw4VLm.lean.js → reference_hooks.md.BHeG-vjK.lean.js} +1 -1
  45. package/dist/docs/assets/{storage.md.mQDtIULc.js → storage.md.DcGSnIxX.js} +10 -4
  46. package/dist/docs/assets/{storage.md.mQDtIULc.lean.js → storage.md.DcGSnIxX.lean.js} +1 -1
  47. package/dist/docs/assets/troubleshooting.md.CkgwmVI0.js +1 -0
  48. package/dist/docs/assets/{troubleshooting.md.B5RVX_tL.lean.js → troubleshooting.md.CkgwmVI0.lean.js} +1 -1
  49. package/dist/docs/building-with-agents.html +5 -5
  50. package/dist/docs/concepts.html +3 -3
  51. package/dist/docs/deployment.html +6 -6
  52. package/dist/docs/evals.html +7 -7
  53. package/dist/docs/example-agents/approval-buddy.html +3 -3
  54. package/dist/docs/example-agents/benny.html +3 -3
  55. package/dist/docs/example-agents/bugbot.html +3 -3
  56. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  57. package/dist/docs/example-agents/codeowners-review.html +3 -3
  58. package/dist/docs/example-agents/concierge.html +3 -3
  59. package/dist/docs/example-agents/fsd.html +3 -3
  60. package/dist/docs/example-agents/index.html +5 -5
  61. package/dist/docs/example-agents/knowledge-base.html +3 -3
  62. package/dist/docs/example-agents/oncall.html +3 -3
  63. package/dist/docs/example-agents/security-reviewer.html +3 -3
  64. package/dist/docs/example-agents/slack-agent.html +3 -3
  65. package/dist/docs/example-agents/weather-agent.html +3 -3
  66. package/dist/docs/guides/agent-to-agent.html +3 -3
  67. package/dist/docs/guides/cloud-runtime.html +3 -3
  68. package/dist/docs/guides/github.html +3 -3
  69. package/dist/docs/guides/human-in-the-loop.html +3 -3
  70. package/dist/docs/guides/mcp-oauth.html +3 -3
  71. package/dist/docs/guides/slack.html +3 -3
  72. package/dist/docs/guides/webhooks.html +49 -6
  73. package/dist/docs/hashmap.json +1 -1
  74. package/dist/docs/hillclimbing.html +3 -3
  75. package/dist/docs/index.html +7 -7
  76. package/dist/docs/quickstart.html +8 -8
  77. package/dist/docs/reference/agent-config.html +3 -3
  78. package/dist/docs/reference/artifacts.html +3 -3
  79. package/dist/docs/reference/channels.html +3 -3
  80. package/dist/docs/reference/cli.html +8 -8
  81. package/dist/docs/reference/connections.html +3 -3
  82. package/dist/docs/reference/hooks.html +5 -5
  83. package/dist/docs/reference/http-api.html +3 -3
  84. package/dist/docs/reference/instructions.html +3 -3
  85. package/dist/docs/reference/playground.html +3 -3
  86. package/dist/docs/reference/project-layout.html +3 -3
  87. package/dist/docs/reference/prompt.html +3 -3
  88. package/dist/docs/reference/schedules.html +3 -3
  89. package/dist/docs/reference/sessions.html +3 -3
  90. package/dist/docs/reference/skills.html +3 -3
  91. package/dist/docs/reference/subagents.html +3 -3
  92. package/dist/docs/reference/tools.html +3 -3
  93. package/dist/docs/scaffolding-agents.html +3 -3
  94. package/dist/docs/storage.html +14 -8
  95. package/dist/docs/troubleshooting.html +5 -5
  96. package/dist/index.d.ts +4 -2
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +3 -1
  99. package/dist/internal/cli-ax.d.ts.map +1 -1
  100. package/dist/internal/cli-ax.js +22 -2
  101. package/dist/internal/cli-deploy.d.ts +6 -0
  102. package/dist/internal/cli-deploy.d.ts.map +1 -1
  103. package/dist/internal/cli-deploy.js +14 -4
  104. package/dist/internal/cli-mcp.js +4 -4
  105. package/dist/internal/deploy-client.js +1 -1
  106. package/dist/internal/discovery.d.ts.map +1 -1
  107. package/dist/internal/discovery.js +35 -7
  108. package/dist/internal/distribution.d.ts.map +1 -1
  109. package/dist/internal/distribution.js +3 -1
  110. package/dist/internal/durable-session-store.d.ts +48 -0
  111. package/dist/internal/durable-session-store.d.ts.map +1 -0
  112. package/dist/internal/durable-session-store.js +86 -0
  113. package/dist/internal/hosting.d.ts +1 -1
  114. package/dist/internal/hosting.js +2 -2
  115. package/dist/internal/init-project.d.ts +34 -0
  116. package/dist/internal/init-project.d.ts.map +1 -1
  117. package/dist/internal/init-project.js +121 -2
  118. package/dist/internal/ledger-serving.d.ts +101 -0
  119. package/dist/internal/ledger-serving.d.ts.map +1 -0
  120. package/dist/internal/ledger-serving.js +174 -0
  121. package/dist/internal/listen-port.d.ts +22 -0
  122. package/dist/internal/listen-port.d.ts.map +1 -0
  123. package/dist/internal/listen-port.js +77 -0
  124. package/dist/internal/playground/static.d.ts +21 -4
  125. package/dist/internal/playground/static.d.ts.map +1 -1
  126. package/dist/internal/playground/static.js +82 -18
  127. package/dist/internal/sdk-runner.d.ts.map +1 -1
  128. package/dist/internal/sdk-runner.js +10 -1
  129. package/dist/internal/server.d.ts.map +1 -1
  130. package/dist/internal/server.js +61 -47
  131. package/dist/internal/session-engine.d.ts +57 -1
  132. package/dist/internal/session-engine.d.ts.map +1 -1
  133. package/dist/internal/session-engine.js +117 -28
  134. package/dist/internal/slack-provision-client.js +1 -1
  135. package/dist/internal/workspace.d.ts +6 -2
  136. package/dist/internal/workspace.d.ts.map +1 -1
  137. package/dist/internal/workspace.js +10 -2
  138. package/dist/kv/memory-kv.d.ts +23 -0
  139. package/dist/kv/memory-kv.d.ts.map +1 -0
  140. package/dist/kv/memory-kv.js +103 -0
  141. package/dist/kv.d.ts +22 -0
  142. package/dist/kv.d.ts.map +1 -0
  143. package/dist/kv.js +20 -0
  144. package/dist/ledger/session-ledger.d.ts +204 -0
  145. package/dist/ledger/session-ledger.d.ts.map +1 -0
  146. package/dist/ledger/session-ledger.js +382 -0
  147. package/dist/ledger.d.ts +11 -0
  148. package/dist/ledger.d.ts.map +1 -0
  149. package/dist/ledger.js +9 -0
  150. package/dist/playground/assets/index-B3HBneZZ.js +85 -0
  151. package/dist/playground/assets/index-Bqk9nplh.css +1 -0
  152. package/dist/playground/index.html +2 -2
  153. package/dist/storage-backends/cursor-hosted.d.ts +25 -3
  154. package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
  155. package/dist/storage-backends/cursor-hosted.js +50 -2
  156. package/dist/storage-backends/file-kv.d.ts +5 -2
  157. package/dist/storage-backends/file-kv.d.ts.map +1 -1
  158. package/dist/storage-backends/file-kv.js +107 -7
  159. package/dist/storage.d.ts +60 -5
  160. package/dist/storage.d.ts.map +1 -1
  161. package/dist/storage.js +28 -4
  162. package/dist/types.d.ts +123 -3
  163. package/dist/types.d.ts.map +1 -1
  164. package/dist/types.js +14 -0
  165. package/docs/README.md +10 -7
  166. package/docs/ab.md +2 -2
  167. package/docs/building-with-agents.md +1 -1
  168. package/docs/deployment.md +34 -11
  169. package/docs/evals.md +3 -3
  170. package/docs/example-agents/index.md +1 -1
  171. package/docs/guides/webhooks.md +87 -0
  172. package/docs/quickstart.md +7 -8
  173. package/docs/reference/cli.md +15 -6
  174. package/docs/reference/hooks.md +8 -2
  175. package/docs/storage.md +67 -7
  176. package/docs/troubleshooting.md +10 -2
  177. package/package.json +20 -7
  178. package/skills/create-agent/SKILL.md +1 -1
  179. package/src/bin/agent-serve.ts +27 -11
  180. package/src/channels/slack/attachments.ts +635 -0
  181. package/src/channels/slack/cursor-account.ts +8 -0
  182. package/src/channels/slack/dispatch.ts +121 -6
  183. package/src/channels/slack/inbound.ts +60 -0
  184. package/src/channels/slack/index.ts +1 -0
  185. package/src/channels/slack/manifest.ts +1 -0
  186. package/src/channels/slack/slack-channel.ts +7 -0
  187. package/src/channels/slack/types.ts +47 -1
  188. package/src/index.ts +9 -0
  189. package/src/internal/cli-ax.ts +25 -1
  190. package/src/internal/cli-deploy.ts +22 -4
  191. package/src/internal/cli-mcp.ts +4 -4
  192. package/src/internal/deploy-client.ts +1 -1
  193. package/src/internal/discovery.ts +42 -5
  194. package/src/internal/distribution.ts +3 -1
  195. package/src/internal/durable-session-store.ts +95 -0
  196. package/src/internal/hosting.ts +2 -2
  197. package/src/internal/init-project.ts +151 -2
  198. package/src/internal/ledger-serving.ts +240 -0
  199. package/src/internal/listen-port.ts +85 -0
  200. package/src/internal/playground/static.ts +111 -20
  201. package/src/internal/sdk-runner.ts +11 -1
  202. package/src/internal/server.ts +68 -61
  203. package/src/internal/session-engine.ts +192 -16
  204. package/src/internal/slack-provision-client.ts +1 -1
  205. package/src/internal/workspace.ts +14 -3
  206. package/src/kv/memory-kv.ts +99 -0
  207. package/src/kv.ts +27 -0
  208. package/src/ledger/session-ledger.ts +535 -0
  209. package/src/ledger.ts +29 -0
  210. package/src/storage-backends/cursor-hosted.ts +108 -3
  211. package/src/storage-backends/file-kv.ts +128 -12
  212. package/src/storage.ts +90 -6
  213. package/src/types.ts +130 -4
  214. package/dist/channels/slack/post-update-delivery.d.ts +0 -85
  215. package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
  216. package/dist/docs/assets/chunks/@localSearchIndexroot.D82_RVtF.js +0 -1
  217. package/dist/docs/assets/example-agents_index.md.DgGBwckv.js +0 -2
  218. package/dist/docs/assets/index.md.CmhptOmN.js +0 -24
  219. package/dist/docs/assets/troubleshooting.md.B5RVX_tL.js +0 -1
  220. package/dist/internal/json-dir-store.d.ts +0 -32
  221. package/dist/internal/json-dir-store.d.ts.map +0 -1
  222. package/dist/internal/persistence-coordinator.d.ts +0 -127
  223. package/dist/internal/persistence-coordinator.d.ts.map +0 -1
  224. package/dist/multi-tenant.d.ts +0 -80
  225. package/dist/multi-tenant.d.ts.map +0 -1
  226. package/dist/multi-tenant.js +0 -69
  227. package/dist/persistence.d.ts +0 -184
  228. package/dist/persistence.d.ts.map +0 -1
  229. package/dist/playground/assets/index-BRhBbnd4.css +0 -1
  230. package/dist/playground/assets/index-Bc1oFgFG.js +0 -85
  231. package/dist/storage-backends/postgres-kv.d.ts +0 -82
  232. package/dist/storage-backends/postgres-kv.d.ts.map +0 -1
  233. package/dist/storage-backends/postgres-kv.js +0 -185
  234. package/src/storage-backends/postgres-kv.ts +0 -253
  235. /package/dist/docs/assets/{ab.md.6cLOW7--.lean.js → ab.md.hdxsvX5M.lean.js} +0 -0
  236. /package/dist/docs/assets/{building-with-agents.md.txrcGU2B.lean.js → building-with-agents.md.PeZaZA1P.lean.js} +0 -0
  237. /package/dist/docs/assets/{evals.md.BQXI3rXy.lean.js → evals.md.qeoGgPw0.lean.js} +0 -0
  238. /package/dist/docs/assets/{example-agents_index.md.DgGBwckv.lean.js → example-agents_index.md.xmJ87d_3.lean.js} +0 -0
  239. /package/dist/docs/assets/{quickstart.md.C_b6ESpD.lean.js → quickstart.md.rIbfahJM.lean.js} +0 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Bind an HTTP server to a port, with a clear next-port path when 3000 (or
3
+ * whatever was requested) is already taken — a common onboarding failure.
4
+ */
5
+
6
+ import { once } from "node:events";
7
+ import type { Server } from "node:http";
8
+ import { CLI_COMMAND_NAME } from "./distribution.js";
9
+
10
+ /** Default listen port for `serve` / `dev` when `--port` is omitted. */
11
+ export const DEFAULT_SERVE_PORT = 3000;
12
+
13
+ /** How many ports above the default to try when the default is taken. */
14
+ const DEFAULT_PORT_FALLBACK_SPAN = 20;
15
+
16
+ export function isAddrInUse(error: unknown): boolean {
17
+ return (
18
+ typeof error === "object" &&
19
+ error !== null &&
20
+ "code" in error &&
21
+ (error as { code?: unknown }).code === "EADDRINUSE"
22
+ );
23
+ }
24
+
25
+ export function formatPortInUseHint(port: number): string {
26
+ const next = port === 0 ? 3001 : port + 1;
27
+ return (
28
+ `port ${port} is already in use. ` +
29
+ `Try \`${CLI_COMMAND_NAME} dev --port ${next}\` or \`--port 0\` for any free port.`
30
+ );
31
+ }
32
+
33
+ /**
34
+ * `server.listen` with fallback when the caller used the default port.
35
+ * Explicit `--port N` (including `0`) fails immediately with a next-port hint.
36
+ */
37
+ export async function listenHttpServer(args: {
38
+ server: Server;
39
+ host: string;
40
+ port: number;
41
+ /** True when the caller passed `port` (including 0); skip fallback. */
42
+ portWasExplicit: boolean;
43
+ logger: (line: string) => void;
44
+ }): Promise<number> {
45
+ const { server, host, logger } = args;
46
+ const startPort = args.port;
47
+ if (args.portWasExplicit || startPort === 0) {
48
+ try {
49
+ server.listen(startPort, host);
50
+ await once(server, "listening");
51
+ } catch (error) {
52
+ if (isAddrInUse(error)) {
53
+ throw new Error(formatPortInUseHint(startPort));
54
+ }
55
+ throw error;
56
+ }
57
+ const address = server.address();
58
+ if (address === null || typeof address === "string") {
59
+ throw new Error("server listening without an address");
60
+ }
61
+ return address.port;
62
+ }
63
+
64
+ const last = startPort + DEFAULT_PORT_FALLBACK_SPAN;
65
+ for (let port = startPort; port <= last; port++) {
66
+ try {
67
+ server.listen(port, host);
68
+ await once(server, "listening");
69
+ if (port !== startPort) {
70
+ logger(
71
+ `[agent-sdk] port ${startPort} in use; listening on ${port} instead (pass --port to pick)`
72
+ );
73
+ }
74
+ return port;
75
+ } catch (error) {
76
+ if (!isAddrInUse(error)) {
77
+ throw error;
78
+ }
79
+ if (port === last) {
80
+ throw new Error(formatPortInUseHint(port));
81
+ }
82
+ }
83
+ }
84
+ throw new Error(formatPortInUseHint(last));
85
+ }
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Serves the built Vite playground SPA and the multi-agent index page.
2
+ * Serves the built Vite playground SPA and the host home page.
3
3
  *
4
4
  * The playground is a Vite + React app (see `playground/`). `serve` auto-builds
5
5
  * `dist/playground/` when the local vite toolchain is present (see
@@ -93,33 +93,111 @@ export function renderPlaygroundAsset(file: string): Response {
93
93
  });
94
94
  }
95
95
 
96
- /** One row on the multi-agent index page. */
96
+ /** Slack surface summary for the host home / `GET /v1/agents`. */
97
+ export type AgentIndexSlack =
98
+ | { kind: "cursor-account"; handle: string }
99
+ | { kind: "socket" };
100
+
101
+ /** One row on the host home page and `GET /v1/agents`. */
97
102
  export interface AgentIndexEntry {
98
103
  slug: string;
99
104
  name: string;
100
105
  model: string;
101
106
  tools: number;
102
107
  channels: number;
108
+ slack?: AgentIndexSlack;
109
+ }
110
+
111
+ export interface HostHomeOptions {
112
+ /** When false, omit `/docs` from the route list. Default true. */
113
+ docs?: boolean;
114
+ /** Same as the serve host's `multi` — mounts `/v1/agents` + host `/v1/health`. */
115
+ multi?: boolean;
103
116
  }
104
117
 
105
- /** Standalone index page listing every mounted agent (multi-agent serving). */
106
- export function renderAgentsIndexHtml(agents: AgentIndexEntry[]): string {
118
+ /**
119
+ * Orienting `/` page: mounted agents (playground links) plus live host routes.
120
+ * Used for both single-agent (empty slug) and multi-agent mounts.
121
+ */
122
+ export function renderAgentsIndexHtml(
123
+ agents: AgentIndexEntry[],
124
+ options: HostHomeOptions = {}
125
+ ): string {
126
+ const docs = options.docs !== false;
127
+ const multi = options.multi === true;
107
128
  const cards = agents
108
129
  .map((agent) => {
130
+ const playground =
131
+ agent.slug === "" ? "/playground" : `/${agent.slug}/playground`;
132
+ const slugLabel = agent.slug === "" ? "/" : `/${agent.slug}`;
109
133
  const meta = `${agent.model || "—"} · ${agent.tools} tool${
110
134
  agent.tools === 1 ? "" : "s"
111
135
  } · ${agent.channels} channel${agent.channels === 1 ? "" : "s"}`;
112
- return ` <a class="card" href="/${escapeHtml(agent.slug)}/playground">
136
+ return ` <a class="card" href="${escapeHtml(playground)}">
113
137
  <div class="card-name">${escapeHtml(agent.name)}</div>
114
- <div class="card-slug">/${escapeHtml(agent.slug)}</div>
138
+ <div class="card-slug">${escapeHtml(slugLabel)} → playground</div>
115
139
  <div class="card-meta">${escapeHtml(meta)}</div>
116
140
  </a>`;
117
141
  })
118
142
  .join("\n");
119
- return AGENTS_INDEX_TEMPLATE.replace(
143
+
144
+ const routes = hostRouteRows(agents, docs, multi)
145
+ .map(
146
+ (row) =>
147
+ ` <tr><td><a href="${escapeHtml(row.href)}"><code>${escapeHtml(row.path)}</code></a></td><td>${escapeHtml(row.blurb)}</td></tr>`
148
+ )
149
+ .join("\n");
150
+
151
+ return HOST_HOME_TEMPLATE.replace(
120
152
  "<!--CARDS-->",
121
153
  cards || ' <div class="card-meta">No agents mounted.</div>'
122
- ).replace("<!--COUNT-->", String(agents.length));
154
+ )
155
+ .replace("<!--COUNT-->", String(agents.length))
156
+ .replace("<!--ROUTES-->", routes);
157
+ }
158
+
159
+ function hostRouteRows(
160
+ agents: AgentIndexEntry[],
161
+ docs: boolean,
162
+ multi: boolean
163
+ ): Array<{ path: string; href: string; blurb: string }> {
164
+ const rows: Array<{ path: string; href: string; blurb: string }> = [
165
+ { path: "/", href: "/", blurb: "This page" },
166
+ ];
167
+ for (const agent of agents) {
168
+ const base = agent.slug === "" ? "" : `/${agent.slug}`;
169
+ const label = agent.slug === "" ? agent.name : agent.slug;
170
+ rows.push({
171
+ path: `${base}/playground`,
172
+ href: `${base}/playground`,
173
+ blurb: `Playground — ${label}`,
174
+ });
175
+ rows.push({
176
+ path: `${base}/v1/health`,
177
+ href: `${base}/v1/health`,
178
+ blurb: `Health — ${label}`,
179
+ });
180
+ }
181
+ if (multi) {
182
+ rows.push({
183
+ path: "/v1/agents",
184
+ href: "/v1/agents",
185
+ blurb: "JSON index of mounted agents",
186
+ });
187
+ rows.push({
188
+ path: "/v1/health",
189
+ href: "/v1/health",
190
+ blurb: "Host liveness",
191
+ });
192
+ }
193
+ if (docs) {
194
+ rows.push({
195
+ path: "/docs",
196
+ href: "/docs/",
197
+ blurb: "Agent SDK documentation",
198
+ });
199
+ }
200
+ return rows;
123
201
  }
124
202
 
125
203
  function escapeHtml(value: string): string {
@@ -137,7 +215,7 @@ function fallbackHtml(baseHref: string): string {
137
215
  <base href="${escapeHtml(baseHref)}" />
138
216
  <meta charset="utf-8" />
139
217
  <meta name="viewport" content="width=device-width, initial-scale=1" />
140
- <title>agent-serve playground</title>
218
+ <title>agent-sdk playground</title>
141
219
  <style>
142
220
  body { margin: 0; background: #0e1116; color: #e6edf3; font: 14px/1.6 ui-sans-serif, system-ui, sans-serif; }
143
221
  .box { max-width: 640px; margin: 64px auto; padding: 0 24px; }
@@ -150,7 +228,7 @@ function fallbackHtml(baseHref: string): string {
150
228
  <div class="box">
151
229
  <h1>Playground bundle not built</h1>
152
230
  <p>
153
- The Vite playground has not been built yet. <code>agent-serve serve</code>
231
+ The Vite playground has not been built yet. <code>agent-sdk serve</code>
154
232
  auto-builds it when the local vite toolchain is available; otherwise run
155
233
  <code>pnpm run build:playground</code> from <code>packages/agent-serve</code>,
156
234
  or <code>pnpm run dev:playground</code> for Vite HMR.
@@ -160,33 +238,46 @@ function fallbackHtml(baseHref: string): string {
160
238
  </html>`;
161
239
  }
162
240
 
163
- const AGENTS_INDEX_TEMPLATE = `<!doctype html>
241
+ const HOST_HOME_TEMPLATE = `<!doctype html>
164
242
  <html lang="en">
165
243
  <head>
166
244
  <meta charset="utf-8" />
167
245
  <meta name="viewport" content="width=device-width, initial-scale=1" />
168
- <title>agent-serve — agents</title>
246
+ <title>Agent SDK</title>
169
247
  <style>
170
248
  :root { color-scheme: dark; }
171
249
  body { margin: 0; background: #0e1116; color: #e6edf3; font: 14px/1.6 ui-sans-serif, system-ui, -apple-system, sans-serif; }
172
- .agents-wrap { max-width: 880px; margin: 0 auto; padding: 48px 24px; }
173
- .agents-wrap h1 { font-size: 20px; font-weight: 650; margin: 0 0 2px; }
174
- .agents-wrap .sub { color: #8b949e; font-size: 13px; margin-bottom: 28px; }
175
- .agents-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
250
+ .wrap { max-width: 880px; margin: 0 auto; padding: 48px 24px; }
251
+ h1 { font-size: 20px; font-weight: 650; margin: 0 0 2px; }
252
+ h2 { font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.04em; color: #8b949e; margin: 36px 0 12px; }
253
+ .sub { color: #8b949e; font-size: 13px; margin-bottom: 28px; }
254
+ .grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
176
255
  .card { display: block; text-decoration: none; color: inherit; background: #161b22; border: 1px solid #30363d; border-radius: 12px; padding: 16px 18px; transition: border-color 0.12s; }
177
256
  .card:hover { border-color: #6cb6ff; }
178
257
  .card-name { font-size: 15px; font-weight: 600; }
179
258
  .card-slug { font-family: ui-monospace, monospace; font-size: 12px; color: #6cb6ff; margin-top: 2px; }
180
259
  .card-meta { color: #6e7681; font-size: 12px; margin-top: 10px; }
260
+ table.routes { width: 100%; border-collapse: collapse; font-size: 13px; }
261
+ table.routes td { padding: 8px 0; border-bottom: 1px solid #21262d; vertical-align: top; }
262
+ table.routes td:first-child { width: 42%; padding-right: 16px; }
263
+ table.routes code { font-family: ui-monospace, monospace; font-size: 12px; color: #6cb6ff; }
264
+ table.routes a { text-decoration: none; }
265
+ table.routes a:hover code { text-decoration: underline; }
266
+ .muted { color: #8b949e; }
181
267
  </style>
182
268
  </head>
183
269
  <body>
184
- <div class="agents-wrap">
185
- <h1>agent-serve</h1>
186
- <div class="sub"><!--COUNT--> agents mounted</div>
187
- <div class="agents-grid">
270
+ <div class="wrap">
271
+ <h1>Agent SDK</h1>
272
+ <div class="sub"><!--COUNT--> agent(s) on this host. Open a playground or use a route below.</div>
273
+ <h2>Agents</h2>
274
+ <div class="grid">
188
275
  <!--CARDS-->
189
276
  </div>
277
+ <h2>Routes</h2>
278
+ <table class="routes">
279
+ <!--ROUTES-->
280
+ </table>
190
281
  </div>
191
282
  </body>
192
283
  </html>`;
@@ -171,7 +171,17 @@ export class CursorSdkRunner implements AgentRunner {
171
171
  const agentPromise = this.getAgent(request);
172
172
  try {
173
173
  const agent = await agentPromise;
174
- const run = await agent.send(request.prompt, {
174
+ const sendMessage =
175
+ request.images !== undefined && request.images.length > 0
176
+ ? {
177
+ text: request.prompt,
178
+ images: request.images.map((image) => ({
179
+ data: image.data,
180
+ mimeType: image.mimeType,
181
+ })),
182
+ }
183
+ : request.prompt;
184
+ const run = await agent.send(sendMessage, {
175
185
  onDelta: async ({ update }) => {
176
186
  try {
177
187
  await request.onUpdate(update);
@@ -99,6 +99,7 @@ import {
99
99
  requireSessionOwner,
100
100
  } from "./http-channel.js";
101
101
  import { validateToolInput } from "./json-schema.js";
102
+ import { DEFAULT_SERVE_PORT, listenHttpServer } from "./listen-port.js";
102
103
  import {
103
104
  createLocalControlPlane,
104
105
  type LocalControlPlane,
@@ -592,52 +593,26 @@ export async function startServer(
592
593
  })),
593
594
  }),
594
595
  });
596
+ }
595
597
 
596
- if (playground) {
597
- const cursorSlackBySlug = new Map(
598
- collectCursorSlackTargets(runtimes).map((target) => [
599
- target.runtime.slug,
600
- target.agentName,
601
- ])
602
- );
603
- const entries: AgentIndexEntry[] = runtimes.map(({ slug, project }) => {
604
- const cursorName = cursorSlackBySlug.get(slug);
605
- const hasSocketSlack = project.channels.some(
606
- (channel) =>
607
- channel.definition.meta?.kind === "slack" &&
608
- channel.definition.meta?.slackCursorAccount !== true
609
- );
610
- const slack =
611
- cursorName !== undefined
612
- ? {
613
- kind: "cursor-account" as const,
614
- handle: `@Cursor ${cursorName}`,
615
- }
616
- : hasSocketSlack
617
- ? { kind: "socket" as const }
618
- : undefined;
619
- return {
620
- slug,
621
- name: project.name,
622
- model:
623
- typeof project.agent.model === "string"
624
- ? project.agent.model
625
- : (project.agent.model?.id ?? ""),
626
- tools: project.agent.tools.length,
627
- channels: project.channels.length + 1,
628
- ...(slack !== undefined ? { slack } : {}),
629
- };
630
- });
631
- const indexHtml = renderAgentsIndexHtml(entries);
632
- router.add({
633
- method: "GET",
634
- pattern: "/",
635
- auth: "none",
636
- handler: () =>
637
- new Response(indexHtml, {
638
- headers: { "content-type": "text/html; charset=utf-8" },
639
- }),
640
- });
598
+ // Orienting `/` for every layout (single empty-slug and multi).
599
+ // Lists playground links plus live routes (playground, health, docs).
600
+ if (playground) {
601
+ const entries = buildAgentIndexEntries(runtimes);
602
+ const indexHtml = renderAgentsIndexHtml(entries, {
603
+ docs: docsSite,
604
+ multi,
605
+ });
606
+ router.add({
607
+ method: "GET",
608
+ pattern: "/",
609
+ auth: "none",
610
+ handler: () =>
611
+ new Response(indexHtml, {
612
+ headers: { "content-type": "text/html; charset=utf-8" },
613
+ }),
614
+ });
615
+ if (multi) {
641
616
  router.add({
642
617
  method: "GET",
643
618
  pattern: "/v1/agents",
@@ -725,10 +700,17 @@ export async function startServer(
725
700
  });
726
701
 
727
702
  const host = options.host ?? "127.0.0.1";
728
- const port = options.port ?? 3000;
703
+ const portWasExplicit = options.port !== undefined;
704
+ const requestedPort = options.port ?? DEFAULT_SERVE_PORT;
705
+ let boundPort: number;
729
706
  try {
730
- server.listen(port, host);
731
- await once(server, "listening");
707
+ boundPort = await listenHttpServer({
708
+ server,
709
+ host,
710
+ port: requestedPort,
711
+ portWasExplicit,
712
+ logger,
713
+ });
732
714
  } catch (error) {
733
715
  await cursorEventRelay?.stop();
734
716
  await cursorSlackRelay?.stop();
@@ -737,7 +719,7 @@ export async function startServer(
737
719
  }
738
720
  const address = server.address() as AddressInfo;
739
721
  const displayHost = host === "0.0.0.0" || host === "::" ? "127.0.0.1" : host;
740
- const url = `http://${displayHost}:${address.port}`;
722
+ const url = `http://${displayHost}:${boundPort}`;
741
723
 
742
724
  // Resolve peer + Cursor account MCP connections now that the listen port is
743
725
  // known (see resolved-connections.ts for the URL/runtime rules).
@@ -1303,18 +1285,6 @@ function mountAgentRoutes(
1303
1285
  auth: "none",
1304
1286
  handler: (_request, ctx) => renderPlaygroundAsset(ctx.params.file ?? ""),
1305
1287
  });
1306
- if (!multi) {
1307
- router.add({
1308
- method: "GET",
1309
- pattern: "/",
1310
- auth: "none",
1311
- handler: () =>
1312
- new Response(null, {
1313
- status: 302,
1314
- headers: { location: "/playground" },
1315
- }),
1316
- });
1317
- }
1318
1288
  }
1319
1289
 
1320
1290
  for (const route of buildHttpChannelRoutes({
@@ -1900,6 +1870,43 @@ function slackAgentNameFromSlug(slug: string): string {
1900
1870
  * resolved. Duplicate names are a startup error — the backend routes
1901
1871
  * `@Cursor <name> …` by name, so two mounts cannot share one.
1902
1872
  */
1873
+ function buildAgentIndexEntries(runtimes: MountedRuntime[]): AgentIndexEntry[] {
1874
+ const cursorSlackBySlug = new Map(
1875
+ collectCursorSlackTargets(runtimes).map((target) => [
1876
+ target.runtime.slug,
1877
+ target.agentName,
1878
+ ])
1879
+ );
1880
+ return runtimes.map(({ slug, project }) => {
1881
+ const cursorName = cursorSlackBySlug.get(slug);
1882
+ const hasSocketSlack = project.channels.some(
1883
+ (channel) =>
1884
+ channel.definition.meta?.kind === "slack" &&
1885
+ channel.definition.meta?.slackCursorAccount !== true
1886
+ );
1887
+ const slack =
1888
+ cursorName !== undefined
1889
+ ? {
1890
+ kind: "cursor-account" as const,
1891
+ handle: `@Cursor ${cursorName}`,
1892
+ }
1893
+ : hasSocketSlack
1894
+ ? { kind: "socket" as const }
1895
+ : undefined;
1896
+ return {
1897
+ slug,
1898
+ name: project.name,
1899
+ model:
1900
+ typeof project.agent.model === "string"
1901
+ ? project.agent.model
1902
+ : (project.agent.model?.id ?? ""),
1903
+ tools: project.agent.tools.length,
1904
+ channels: project.channels.length + 1,
1905
+ slack,
1906
+ };
1907
+ });
1908
+ }
1909
+
1903
1910
  function collectCursorSlackTargets(
1904
1911
  runtimes: MountedRuntime[]
1905
1912
  ): CursorSlackTarget[] {