@cursor/july 0.1.34 → 0.1.35

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 (75) hide show
  1. package/dist/docs/404.html +1 -1
  2. package/dist/docs/ab.html +2 -2
  3. package/dist/docs/assets/{app.FPupl4SP.js → app.D5Mv1T0U.js} +1 -1
  4. package/dist/docs/assets/chunks/@localSearchIndexroot.Cu7b6o1D.js +1 -0
  5. package/dist/docs/assets/chunks/{VPLocalSearchBox.Cd182Cu0.js → VPLocalSearchBox.CMq_BQce.js} +1 -1
  6. package/dist/docs/assets/chunks/{theme.BEM3Okcd.js → theme.C6D9UPLK.js} +2 -2
  7. package/dist/docs/building-with-agents.html +2 -2
  8. package/dist/docs/concepts.html +2 -2
  9. package/dist/docs/deployment.html +2 -2
  10. package/dist/docs/evals.html +2 -2
  11. package/dist/docs/example-agents/approval-buddy.html +2 -2
  12. package/dist/docs/example-agents/benny.html +2 -2
  13. package/dist/docs/example-agents/bugbot.html +2 -2
  14. package/dist/docs/example-agents/codebase-wiki.html +2 -2
  15. package/dist/docs/example-agents/codeowners-review.html +2 -2
  16. package/dist/docs/example-agents/concierge.html +2 -2
  17. package/dist/docs/example-agents/fsd.html +2 -2
  18. package/dist/docs/example-agents/index.html +2 -2
  19. package/dist/docs/example-agents/knowledge-base.html +2 -2
  20. package/dist/docs/example-agents/oncall.html +2 -2
  21. package/dist/docs/example-agents/security-reviewer.html +2 -2
  22. package/dist/docs/example-agents/slack-agent.html +2 -2
  23. package/dist/docs/example-agents/weather-agent.html +2 -2
  24. package/dist/docs/guides/agent-to-agent.html +2 -2
  25. package/dist/docs/guides/cloud-runtime.html +2 -2
  26. package/dist/docs/guides/github.html +2 -2
  27. package/dist/docs/guides/human-in-the-loop.html +2 -2
  28. package/dist/docs/guides/mcp-oauth.html +2 -2
  29. package/dist/docs/guides/slack.html +2 -2
  30. package/dist/docs/guides/webhooks.html +2 -2
  31. package/dist/docs/hillclimbing.html +2 -2
  32. package/dist/docs/index.html +2 -2
  33. package/dist/docs/quickstart.html +2 -2
  34. package/dist/docs/reference/agent-config.html +2 -2
  35. package/dist/docs/reference/channels.html +2 -2
  36. package/dist/docs/reference/cli.html +2 -2
  37. package/dist/docs/reference/connections.html +2 -2
  38. package/dist/docs/reference/hooks.html +2 -2
  39. package/dist/docs/reference/http-api.html +2 -2
  40. package/dist/docs/reference/instructions.html +2 -2
  41. package/dist/docs/reference/playground.html +2 -2
  42. package/dist/docs/reference/project-layout.html +2 -2
  43. package/dist/docs/reference/prompt.html +2 -2
  44. package/dist/docs/reference/schedules.html +2 -2
  45. package/dist/docs/reference/sessions.html +2 -2
  46. package/dist/docs/reference/skills.html +2 -2
  47. package/dist/docs/reference/subagents.html +2 -2
  48. package/dist/docs/reference/tools.html +2 -2
  49. package/dist/docs/scaffolding-agents.html +2 -2
  50. package/dist/docs/storage.html +2 -2
  51. package/dist/docs/troubleshooting.html +2 -2
  52. package/dist/internal/host-kv.d.ts +6 -2
  53. package/dist/internal/host-kv.d.ts.map +1 -1
  54. package/dist/internal/session-engine.d.ts.map +1 -1
  55. package/dist/internal/session-engine.js +15 -6
  56. package/dist/internal/storage-coordinator.d.ts +9 -1
  57. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  58. package/dist/internal/storage-coordinator.js +7 -0
  59. package/dist/internal/storage-roles.d.ts +78 -0
  60. package/dist/internal/storage-roles.d.ts.map +1 -0
  61. package/dist/internal/storage-roles.js +24 -0
  62. package/dist/internal/workspace.d.ts +26 -0
  63. package/dist/internal/workspace.d.ts.map +1 -1
  64. package/dist/internal/workspace.js +53 -0
  65. package/dist/playground/assets/{index-CDDWw0YX.js → index-D7OV8B_H.js} +40 -40
  66. package/dist/playground/assets/index-DOb96C0M.css +1 -0
  67. package/dist/playground/index.html +2 -2
  68. package/package.json +1 -1
  69. package/src/internal/host-kv.ts +6 -2
  70. package/src/internal/session-engine.ts +20 -8
  71. package/src/internal/storage-coordinator.ts +15 -1
  72. package/src/internal/storage-roles.ts +86 -0
  73. package/src/internal/workspace.ts +62 -1
  74. package/dist/docs/assets/chunks/@localSearchIndexroot.WoYunhnT.js +0 -1
  75. package/dist/playground/assets/index-MVuNTd8v.css +0 -1
@@ -8,8 +8,8 @@
8
8
  />
9
9
  <meta name="viewport" content="width=device-width, initial-scale=1" />
10
10
  <title>agent-serve playground</title>
11
- <script type="module" crossorigin src="./assets/index-CDDWw0YX.js"></script>
12
- <link rel="stylesheet" crossorigin href="./assets/index-MVuNTd8v.css">
11
+ <script type="module" crossorigin src="./assets/index-D7OV8B_H.js"></script>
12
+ <link rel="stylesheet" crossorigin href="./assets/index-DOb96C0M.css">
13
13
  </head>
14
14
  <body>
15
15
  <div id="root"></div>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cursor/july",
3
- "version": "0.1.34",
3
+ "version": "0.1.35",
4
4
  "description": "(early alpha) Filesystem-first framework for defining Cursor agents as markdown and TypeScript and serving them over channels with the Cursor SDK.",
5
5
  "license": "SEE LICENSE IN LICENSE.md",
6
6
  "repository": {
@@ -11,12 +11,16 @@ import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
11
11
  import { join } from "node:path";
12
12
  import { storageKeys } from "../storage.js";
13
13
  import type { HostKvApi, JsonValue } from "../types.js";
14
- import type { StorageCoordinator } from "./storage-coordinator.js";
14
+ import type { AuthorKvStore } from "./storage-roles.js";
15
15
 
16
16
  export interface CreateHostKvApiOptions {
17
17
  agentName: string;
18
18
  stateRoot: string;
19
- storage?: StorageCoordinator;
19
+ /**
20
+ * Author KV backend. Narrowed to {@link AuthorKvStore} so this path cannot
21
+ * reach framework session state, which is headed somewhere else entirely.
22
+ */
23
+ storage?: AuthorKvStore;
20
24
  }
21
25
 
22
26
  /** Build the host KV API for one agent process. */
@@ -103,6 +103,7 @@ import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections
103
103
  import type { AgentRunner } from "./sdk-runner.js";
104
104
  import { SessionStore } from "./session-store.js";
105
105
  import { StorageCoordinator } from "./storage-coordinator.js";
106
+ import type { FrameworkSessionStore } from "./storage-roles.js";
106
107
  import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
107
108
  import {
108
109
  type EstimateTurnCostFn,
@@ -115,6 +116,7 @@ import {
115
116
  buildCloudMemorySection,
116
117
  isNestedInGitRepo,
117
118
  materializeWorkspace,
119
+ SERVER_TOOLS_MCP_SERVER_NAME,
118
120
  writeWorkspaceFiles,
119
121
  } from "./workspace.js";
120
122
 
@@ -1463,8 +1465,18 @@ export class SessionEngine {
1463
1465
  if (runtime === "cloud") {
1464
1466
  // Cloud VMs have no materialized workspace: ship the full context,
1465
1467
  // including agent tool script bodies. On hosted deployments the
1466
- // shared memory journal is on the store mount — say where.
1467
- const preamble = buildAgentsMdContent(agent, { includeScripts: true });
1468
+ // shared memory journal is on the store mount — say where. When the
1469
+ // turn carries the session-bound server-tool MCP endpoint, the
1470
+ // preamble also catalogs server tools under that MCP server name;
1471
+ // without it a model told to "use <server tool>" finds no script
1472
+ // and improvises a local substitute instead of calling the host.
1473
+ const preamble = buildAgentsMdContent(agent, {
1474
+ includeScripts: true,
1475
+ serverToolsMcpName:
1476
+ this.cloudServerToolsConnection === undefined
1477
+ ? undefined
1478
+ : SERVER_TOOLS_MCP_SERVER_NAME,
1479
+ });
1468
1480
  const memorySection = isCursorHostedFilesAvailable()
1469
1481
  ? `\n\n${buildCloudMemorySection(this.project.name)}`
1470
1482
  : "";
@@ -1677,16 +1689,16 @@ export class SessionEngine {
1677
1689
  this.cloudServerToolsConnection !== undefined &&
1678
1690
  this.project.agent.tools.some((tool) => tool.execution === "server")
1679
1691
  ) {
1680
- if (servers["agentsdk-tools"] !== undefined) {
1692
+ if (servers[SERVER_TOOLS_MCP_SERVER_NAME] !== undefined) {
1681
1693
  throw new Error(
1682
- 'MCP connection name "agentsdk-tools" is reserved for authored server tools on cloud turns.'
1694
+ `MCP connection name "${SERVER_TOOLS_MCP_SERVER_NAME}" is reserved for authored server tools on cloud turns.`
1683
1695
  );
1684
1696
  }
1685
1697
  const connection = this.cloudServerToolsConnection;
1686
1698
  if ("url" in connection) {
1687
1699
  const url = new URL(connection.url);
1688
1700
  url.searchParams.set("agentkitSessionId", sessionId);
1689
- servers["agentsdk-tools"] = {
1701
+ servers[SERVER_TOOLS_MCP_SERVER_NAME] = {
1690
1702
  ...connection,
1691
1703
  url: url.toString(),
1692
1704
  };
@@ -2938,7 +2950,7 @@ export class SessionEngine {
2938
2950
  }
2939
2951
 
2940
2952
  private async runBulkRestore(
2941
- storage: StorageCoordinator,
2953
+ storage: FrameworkSessionStore,
2942
2954
  policy: { maxSessions: number; maxAgeMs: number; maxTotalBytes: number }
2943
2955
  ): Promise<void> {
2944
2956
  const loaded = await storage.listSessions();
@@ -3099,7 +3111,7 @@ export class SessionEngine {
3099
3111
  }
3100
3112
 
3101
3113
  private async loadAndMaterializeByContinuation(
3102
- storage: StorageCoordinator,
3114
+ storage: FrameworkSessionStore,
3103
3115
  channelId: string,
3104
3116
  continuationKey: string
3105
3117
  ): Promise<SessionRecord | undefined> {
@@ -3115,7 +3127,7 @@ export class SessionEngine {
3115
3127
  }
3116
3128
 
3117
3129
  private async loadAndMaterializeById(
3118
- storage: StorageCoordinator,
3130
+ storage: FrameworkSessionStore,
3119
3131
  sessionId: string
3120
3132
  ): Promise<SessionRecord | undefined> {
3121
3133
  const record = await storage.getSessionById(sessionId);
@@ -14,6 +14,13 @@
14
14
  * and A/B metrics go to their dedicated tables (`evals` / `abs`).
15
15
  * Reminder documents live on the control plane. Everything shares one
16
16
  * bounded, serialized queue.
17
+ *
18
+ * It does three unrelated jobs, declared separately as
19
+ * {@link FrameworkSessionStore}, {@link AuthorKvStore}, and
20
+ * {@link EvalAbStore} — see `storage-roles.ts` for why they differ and where
21
+ * each is headed. The queue stays shared: it is one bounded, serialized chain
22
+ * per agent, and splitting it would change backpressure behavior, which this
23
+ * seam deliberately does not touch.
17
24
  */
18
25
 
19
26
  import type { ABMetricSample } from "../ab.js";
@@ -30,6 +37,11 @@ import type { JsonValue, SessionEvent, SessionRecord } from "../types.js";
30
37
  import type { ABSnapshot } from "./ab-snapshot.js";
31
38
  import { describeError } from "./describe-error.js";
32
39
  import type { EvalRunStorage } from "./eval-run-store.js";
40
+ import type {
41
+ AuthorKvStore,
42
+ EvalAbStore,
43
+ FrameworkSessionStore,
44
+ } from "./storage-roles.js";
33
45
 
34
46
  /** Event types that close a unit of work — flush point for turn-end batching. */
35
47
  const TURN_BOUNDARY_EVENTS: ReadonlySet<SessionEvent["type"]> = new Set([
@@ -85,7 +97,9 @@ interface SessionBuffer {
85
97
  debounceTimer?: ReturnType<typeof setTimeout>;
86
98
  }
87
99
 
88
- export class StorageCoordinator {
100
+ export class StorageCoordinator
101
+ implements FrameworkSessionStore, AuthorKvStore, EvalAbStore
102
+ {
89
103
  readonly policy: ResolvedStoragePolicy;
90
104
 
91
105
  private readonly definition: StorageDefinition;
@@ -0,0 +1,86 @@
1
+ /**
2
+ * The three unrelated jobs `defineStorage` currently does, named separately.
3
+ *
4
+ * {@link import("./storage-coordinator.js").StorageCoordinator} implements all
5
+ * three today over one sink and one delivery queue. Splitting the *types* is
6
+ * the seam that lets each job change independently:
7
+ *
8
+ * - {@link FrameworkSessionStore} is framework state. Session records, event
9
+ * chunks, and the continuation index are how a session survives the process,
10
+ * so they belong to Agent Serve, not to the agent. Today they persist only
11
+ * when the author writes `agent/storage.ts` — the defect this seam exists to
12
+ * let us fix, by giving the framework its own provider that is automatic and
13
+ * not author-configurable.
14
+ * - {@link AuthorKvStore} is author state: whatever `ctx.host.kv` was handed.
15
+ * It converges onto the author file plane, which is a different destination
16
+ * from the one above.
17
+ * - {@link EvalAbStore} is playground/experiment bookkeeping with its own
18
+ * dedicated tables, and follows neither.
19
+ *
20
+ * Consumers should depend on the narrowest role they use. That is what keeps a
21
+ * later change to framework persistence from silently reaching author KV, and
22
+ * it is why these are interfaces rather than a comment on one class.
23
+ */
24
+
25
+ import type { ABMetricSample } from "../ab.js";
26
+ import type { StorageContext } from "../storage.js";
27
+ import type { JsonValue, SessionEvent, SessionRecord } from "../types.js";
28
+ import type { ABSnapshot } from "./ab-snapshot.js";
29
+ import type { EvalRunStorage } from "./eval-run-store.js";
30
+
31
+ /**
32
+ * Framework-owned session durability: what has to be true for a session to
33
+ * outlive the process that served it.
34
+ *
35
+ * Writes are fire-and-forget by design — a throwing backend is logged and its
36
+ * write dropped, because storage must never stall or fail a turn. That is
37
+ * at-most-once, and it is the property Track F later has to tighten, since a
38
+ * dropped record is a session that cannot be restored.
39
+ */
40
+ export interface FrameworkSessionStore {
41
+ /** Durable session-record update; coalesced until the next flush. */
42
+ sessionRecord(record: SessionRecord): void;
43
+ /** One appended session event; batched per turn or debounce window. */
44
+ event(event: SessionEvent): void;
45
+ /** Marks an event delivered, for backends that track dispatch. */
46
+ eventDispatched(event: SessionEvent): void;
47
+ /** Flush everything buffered for one session (ordered: events, record). */
48
+ flushSession(sessionId: string, reason?: StorageContext["reason"]): void;
49
+
50
+ /** True when the backend can list every session in one bulk read. */
51
+ readonly canBulkRestore: boolean;
52
+ /** True when the backend is the Cursor-hosted one rather than an authored sink. */
53
+ readonly isCursorHosted: boolean;
54
+
55
+ listSessions(): Promise<SessionRecord[]>;
56
+ listSessionsFromSink(): Promise<SessionRecord[]>;
57
+ listSessionEvents(sessionId: string): Promise<SessionEvent[]>;
58
+ getSessionById(sessionId: string): Promise<SessionRecord | undefined>;
59
+ getSessionByContinuation(
60
+ channelId: string,
61
+ continuationKey: string
62
+ ): Promise<SessionRecord | undefined>;
63
+ }
64
+
65
+ /**
66
+ * Author-facing durable JSON behind `ctx.host.kv`.
67
+ *
68
+ * Deployment-wide on purpose: the point is state an author saves in one
69
+ * session and reads back in a later one, so unlike `ctx.host.files` there is
70
+ * no session scope to default to.
71
+ */
72
+ export interface AuthorKvStore {
73
+ /** False when no backend is configured; callers fall back to local disk. */
74
+ readonly supportsAuthorKv: boolean;
75
+ kvGet(authorKey: string): Promise<JsonValue | undefined>;
76
+ kvPut(authorKey: string, value: JsonValue): Promise<void>;
77
+ kvDelete(authorKey: string): Promise<void>;
78
+ }
79
+
80
+ /** Playground eval batches and live A/B metrics, on their own tables. */
81
+ export interface EvalAbStore {
82
+ evalRuns(): EvalRunStorage | undefined;
83
+ abSample(sample: ABMetricSample): void;
84
+ abSnapshot(snapshot: ABSnapshot): void;
85
+ getLatestAbSnapshot(): Promise<ABSnapshot | undefined>;
86
+ }
@@ -66,6 +66,14 @@ export function agentToolScriptPath(toolName: string): string {
66
66
  return `.agent-serve/tools/${toolName}.sh`;
67
67
  }
68
68
 
69
+ /**
70
+ * Reserved MCP connection name under which authored server tools are served
71
+ * to cloud turns (the session-bound tool MCP endpoint). Shared between the
72
+ * turn's MCP config and the first-prompt server-tool catalog so the model is
73
+ * told exactly where those tools live.
74
+ */
75
+ export const SERVER_TOOLS_MCP_SERVER_NAME = "agentsdk-tools";
76
+
69
77
  /**
70
78
  * Always-on identity line prepended when composing instructions. Sourced from
71
79
  * {@link ResolvedAgent.name} (`defineAgent({ name })`, else package/dir name)
@@ -157,16 +165,64 @@ export function buildAgentToolsCatalog(
157
165
  return `${parts.join("\n").trimEnd()}\n`;
158
166
  }
159
167
 
168
+ /**
169
+ * Markdown catalog of authored server tools for the cloud first prompt.
170
+ *
171
+ * Server tools execute on the serve host and reach cloud turns only as MCP
172
+ * tools on {@link SERVER_TOOLS_MCP_SERVER_NAME}. Instructions reference them
173
+ * by bare name, and the agent-tool catalog teaches a "recreate the script if
174
+ * missing" pattern — without this section a cloud model that cannot find a
175
+ * script for a server tool improvises a local substitute instead of calling
176
+ * the real tool, so its host-side effects silently never happen.
177
+ */
178
+ export function buildServerToolsCatalog(
179
+ tools: DiscoveredTool[],
180
+ options: { mcpServerName: string }
181
+ ): string {
182
+ const serverTools = tools.filter((tool) => tool.execution === "server");
183
+ if (serverTools.length === 0) {
184
+ return "";
185
+ }
186
+
187
+ const parts: string[] = [
188
+ "## Server tools",
189
+ "",
190
+ `These tools execute on the agent-serve host, not in this environment. They are available ONLY as MCP tools on the \`${options.mcpServerName}\` MCP server — list that server's tools and call them by name through your MCP tooling.`,
191
+ "There are no local scripts for these tools. NEVER create, edit, or run a local script or other substitute for them: a local re-implementation cannot produce their real effects on the host.",
192
+ "",
193
+ ];
194
+
195
+ for (const tool of serverTools) {
196
+ parts.push(`### \`${tool.name}\``);
197
+ parts.push("");
198
+ parts.push(tool.description.trim());
199
+ parts.push("");
200
+ parts.push("Input schema:");
201
+ parts.push("");
202
+ parts.push("```json");
203
+ parts.push(JSON.stringify(tool.inputSchema, null, 2));
204
+ parts.push("```");
205
+ parts.push("");
206
+ }
207
+
208
+ return `${parts.join("\n").trimEnd()}\n`;
209
+ }
210
+
160
211
  /**
161
212
  * The full AGENTS.md-equivalent context for an agent: instructions, identity
162
213
  * section, and the agent-side tool catalog. Written to the session workspace
163
214
  * as `AGENTS.md`, and inlined into the first turn's prompt when the harness
164
215
  * cannot discover that file (cloud runtime, or a local workspace the harness
165
216
  * will not treat as the project root — see {@link isNestedInGitRepo}).
217
+ *
218
+ * `serverToolsMcpName` is cloud-only: when the turn's MCP config carries the
219
+ * session-bound server-tool endpoint, the catalog names that server so the
220
+ * model calls the real tools instead of inventing local stand-ins. Local
221
+ * runtimes omit it — there the harness exposes server tools natively.
166
222
  */
167
223
  export function buildAgentsMdContent(
168
224
  agent: ResolvedAgent,
169
- options: { includeScripts: boolean }
225
+ options: { includeScripts: boolean; serverToolsMcpName?: string }
170
226
  ): string {
171
227
  return [
172
228
  agent.instructions?.trim() ?? "",
@@ -174,6 +230,11 @@ export function buildAgentsMdContent(
174
230
  buildAgentToolsCatalog(agent.tools, {
175
231
  includeScripts: options.includeScripts,
176
232
  }),
233
+ options.serverToolsMcpName === undefined
234
+ ? ""
235
+ : buildServerToolsCatalog(agent.tools, {
236
+ mcpServerName: options.serverToolsMcpName,
237
+ }),
177
238
  ]
178
239
  .filter((part) => part !== "")
179
240
  .join("\n\n");