@cursor/july 0.1.33 → 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 (171) hide show
  1. package/README.md +1 -1
  2. package/dist/artifacts.d.ts.map +1 -1
  3. package/dist/artifacts.js +0 -5
  4. package/dist/bin/agent-serve.d.ts +1 -0
  5. package/dist/bin/agent-serve.d.ts.map +1 -1
  6. package/dist/bin/agent-serve.js +32 -2
  7. package/dist/channels/github/types.d.ts +10 -7
  8. package/dist/channels/github/types.d.ts.map +1 -1
  9. package/dist/docs/404.html +2 -2
  10. package/dist/docs/ab.html +3 -3
  11. package/dist/docs/assets/{app.CgZqLyWL.js → app.D5Mv1T0U.js} +1 -1
  12. package/dist/docs/assets/chunks/@localSearchIndexroot.Cu7b6o1D.js +1 -0
  13. package/dist/docs/assets/chunks/{VPLocalSearchBox.C4KigoQn.js → VPLocalSearchBox.CMq_BQce.js} +1 -1
  14. package/dist/docs/assets/chunks/{theme.C_Ty4yT7.js → theme.C6D9UPLK.js} +2 -2
  15. package/dist/docs/assets/{example-agents_weather-agent.md.Dmrcphhl.js → example-agents_weather-agent.md.BADkPqxQ.js} +6 -4
  16. package/dist/docs/assets/example-agents_weather-agent.md.BADkPqxQ.lean.js +1 -0
  17. package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.js +9 -0
  18. package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.lean.js +1 -0
  19. package/dist/docs/building-with-agents.html +3 -3
  20. package/dist/docs/concepts.html +3 -3
  21. package/dist/docs/deployment.html +3 -3
  22. package/dist/docs/evals.html +3 -3
  23. package/dist/docs/example-agents/approval-buddy.html +3 -3
  24. package/dist/docs/example-agents/benny.html +3 -3
  25. package/dist/docs/example-agents/bugbot.html +3 -3
  26. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  27. package/dist/docs/example-agents/codeowners-review.html +3 -3
  28. package/dist/docs/example-agents/concierge.html +3 -3
  29. package/dist/docs/example-agents/fsd.html +3 -3
  30. package/dist/docs/example-agents/index.html +3 -3
  31. package/dist/docs/example-agents/knowledge-base.html +3 -3
  32. package/dist/docs/example-agents/oncall.html +3 -3
  33. package/dist/docs/example-agents/security-reviewer.html +3 -3
  34. package/dist/docs/example-agents/slack-agent.html +3 -3
  35. package/dist/docs/example-agents/weather-agent.html +10 -8
  36. package/dist/docs/guides/agent-to-agent.html +3 -3
  37. package/dist/docs/guides/cloud-runtime.html +5 -5
  38. package/dist/docs/guides/github.html +3 -3
  39. package/dist/docs/guides/human-in-the-loop.html +3 -3
  40. package/dist/docs/guides/mcp-oauth.html +3 -3
  41. package/dist/docs/guides/slack.html +3 -3
  42. package/dist/docs/guides/webhooks.html +3 -3
  43. package/dist/docs/hashmap.json +1 -1
  44. package/dist/docs/hillclimbing.html +3 -3
  45. package/dist/docs/index.html +3 -3
  46. package/dist/docs/quickstart.html +3 -3
  47. package/dist/docs/reference/agent-config.html +3 -3
  48. package/dist/docs/reference/channels.html +3 -3
  49. package/dist/docs/reference/cli.html +3 -3
  50. package/dist/docs/reference/connections.html +3 -3
  51. package/dist/docs/reference/hooks.html +3 -3
  52. package/dist/docs/reference/http-api.html +3 -3
  53. package/dist/docs/reference/instructions.html +3 -3
  54. package/dist/docs/reference/playground.html +3 -3
  55. package/dist/docs/reference/project-layout.html +3 -3
  56. package/dist/docs/reference/prompt.html +3 -3
  57. package/dist/docs/reference/schedules.html +3 -3
  58. package/dist/docs/reference/sessions.html +3 -3
  59. package/dist/docs/reference/skills.html +3 -3
  60. package/dist/docs/reference/subagents.html +3 -3
  61. package/dist/docs/reference/tools.html +3 -3
  62. package/dist/docs/scaffolding-agents.html +3 -3
  63. package/dist/docs/storage.html +3 -3
  64. package/dist/docs/troubleshooting.html +3 -3
  65. package/dist/files-backends/agent-store-presigned-url.d.ts +3 -11
  66. package/dist/files.d.ts +20 -0
  67. package/dist/files.d.ts.map +1 -1
  68. package/dist/files.js +20 -0
  69. package/dist/index.d.ts +2 -2
  70. package/dist/index.d.ts.map +1 -1
  71. package/dist/index.js +2 -2
  72. package/dist/internal/artifacts-store.d.ts +14 -6
  73. package/dist/internal/artifacts-store.d.ts.map +1 -1
  74. package/dist/internal/artifacts-store.js +160 -16
  75. package/dist/internal/cli-convert-automation.d.ts +39 -0
  76. package/dist/internal/cli-convert-automation.d.ts.map +1 -0
  77. package/dist/internal/cli-convert-automation.js +280 -0
  78. package/dist/internal/cli-deploy.d.ts +2 -0
  79. package/dist/internal/cli-deploy.d.ts.map +1 -1
  80. package/dist/internal/cli-deploy.js +1 -1
  81. package/dist/internal/cli-mcp-oauth.d.ts +23 -0
  82. package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
  83. package/dist/internal/cli-mcp-oauth.js +57 -33
  84. package/dist/internal/convert-automation/convert-workflow.d.ts +10 -0
  85. package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -0
  86. package/dist/internal/convert-automation/convert-workflow.js +998 -0
  87. package/dist/internal/convert-automation/index.d.ts +3 -0
  88. package/dist/internal/convert-automation/index.d.ts.map +1 -0
  89. package/dist/internal/convert-automation/index.js +1 -0
  90. package/dist/internal/convert-automation/slug.d.ts +11 -0
  91. package/dist/internal/convert-automation/slug.d.ts.map +1 -0
  92. package/dist/internal/convert-automation/slug.js +36 -0
  93. package/dist/internal/convert-automation/types.d.ts +191 -0
  94. package/dist/internal/convert-automation/types.d.ts.map +1 -0
  95. package/dist/internal/convert-automation/types.js +13 -0
  96. package/dist/internal/deploy-client.d.ts +31 -0
  97. package/dist/internal/deploy-client.d.ts.map +1 -1
  98. package/dist/internal/deploy-client.js +48 -0
  99. package/dist/internal/discovery.d.ts.map +1 -1
  100. package/dist/internal/discovery.js +3 -32
  101. package/dist/internal/host-kv.d.ts +6 -2
  102. package/dist/internal/host-kv.d.ts.map +1 -1
  103. package/dist/internal/init-project.d.ts +1 -0
  104. package/dist/internal/init-project.d.ts.map +1 -1
  105. package/dist/internal/init-project.js +1 -1
  106. package/dist/internal/mcp-endpoint.d.ts +6 -0
  107. package/dist/internal/mcp-endpoint.d.ts.map +1 -1
  108. package/dist/internal/mcp-endpoint.js +168 -0
  109. package/dist/internal/resolved-connections.d.ts +15 -0
  110. package/dist/internal/resolved-connections.d.ts.map +1 -1
  111. package/dist/internal/resolved-connections.js +12 -5
  112. package/dist/internal/server.d.ts.map +1 -1
  113. package/dist/internal/server.js +75 -7
  114. package/dist/internal/session-engine.d.ts +56 -3
  115. package/dist/internal/session-engine.d.ts.map +1 -1
  116. package/dist/internal/session-engine.js +262 -84
  117. package/dist/internal/storage-coordinator.d.ts +9 -16
  118. package/dist/internal/storage-coordinator.d.ts.map +1 -1
  119. package/dist/internal/storage-coordinator.js +7 -52
  120. package/dist/internal/storage-roles.d.ts +78 -0
  121. package/dist/internal/storage-roles.d.ts.map +1 -0
  122. package/dist/internal/storage-roles.js +24 -0
  123. package/dist/internal/workspace.d.ts +36 -5
  124. package/dist/internal/workspace.d.ts.map +1 -1
  125. package/dist/internal/workspace.js +66 -0
  126. package/dist/memory.d.ts +22 -6
  127. package/dist/memory.d.ts.map +1 -1
  128. package/dist/memory.js +100 -8
  129. package/dist/playground/assets/{index-CVlCAcUx.js → index-D7OV8B_H.js} +50 -50
  130. package/dist/playground/assets/index-DOb96C0M.css +1 -0
  131. package/dist/playground/index.html +2 -2
  132. package/dist/storage.d.ts +0 -4
  133. package/dist/storage.d.ts.map +1 -1
  134. package/dist/storage.js +0 -4
  135. package/dist/types.d.ts +18 -0
  136. package/dist/types.d.ts.map +1 -1
  137. package/docs/example-agents/weather-agent.md +29 -11
  138. package/docs/guides/cloud-runtime.md +6 -4
  139. package/package.json +3 -1
  140. package/src/artifacts.ts +0 -6
  141. package/src/bin/agent-serve.ts +36 -2
  142. package/src/channels/github/types.ts +10 -7
  143. package/src/files.ts +27 -0
  144. package/src/index.ts +6 -1
  145. package/src/internal/artifacts-store.ts +185 -22
  146. package/src/internal/cli-convert-automation.ts +352 -0
  147. package/src/internal/cli-deploy.ts +1 -1
  148. package/src/internal/cli-mcp-oauth.ts +69 -31
  149. package/src/internal/convert-automation/convert-workflow.ts +1422 -0
  150. package/src/internal/convert-automation/index.ts +10 -0
  151. package/src/internal/convert-automation/slug.ts +38 -0
  152. package/src/internal/convert-automation/types.ts +199 -0
  153. package/src/internal/deploy-client.ts +78 -0
  154. package/src/internal/discovery.ts +3 -37
  155. package/src/internal/host-kv.ts +6 -2
  156. package/src/internal/init-project.ts +1 -1
  157. package/src/internal/mcp-endpoint.ts +243 -0
  158. package/src/internal/resolved-connections.ts +32 -6
  159. package/src/internal/server.ts +99 -1
  160. package/src/internal/session-engine.ts +345 -103
  161. package/src/internal/storage-coordinator.ts +15 -63
  162. package/src/internal/storage-roles.ts +86 -0
  163. package/src/internal/workspace.ts +81 -6
  164. package/src/memory.ts +126 -10
  165. package/src/storage.ts +0 -6
  166. package/src/types.ts +15 -0
  167. package/dist/docs/assets/chunks/@localSearchIndexroot.B1uHGxBZ.js +0 -1
  168. package/dist/docs/assets/example-agents_weather-agent.md.Dmrcphhl.lean.js +0 -1
  169. package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.js +0 -9
  170. package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.lean.js +0 -1
  171. package/dist/playground/assets/index-BRhBbnd4.css +0 -1
@@ -9,7 +9,7 @@
9
9
 
10
10
  import { mkdir, rm, writeFile } from "node:fs/promises";
11
11
  import { dirname, join } from "node:path";
12
- import type { SDKCustomTool } from "@cursor/sdk";
12
+ import type { SDKCustomTool, SDKCustomToolResult } from "@cursor/sdk";
13
13
  import {
14
14
  type ABDefinition,
15
15
  mergeABInstructions,
@@ -22,6 +22,7 @@ import {
22
22
  SlackNudgeStore,
23
23
  } from "../channels/slack/nudge-store.js";
24
24
  import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
25
+ import { isCursorHostedFilesAvailable } from "../files-backends/cursor-hosted.js";
25
26
  import {
26
27
  type AgentProject,
27
28
  type ApprovalDecision,
@@ -102,6 +103,7 @@ import { assertCloudCanHonorAccountServersFilters } from "./resolved-connections
102
103
  import type { AgentRunner } from "./sdk-runner.js";
103
104
  import { SessionStore } from "./session-store.js";
104
105
  import { StorageCoordinator } from "./storage-coordinator.js";
106
+ import type { FrameworkSessionStore } from "./storage-roles.js";
105
107
  import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
106
108
  import {
107
109
  type EstimateTurnCostFn,
@@ -111,8 +113,10 @@ import {
111
113
  import { TurnGovernor } from "./turn-governor.js";
112
114
  import {
113
115
  buildAgentsMdContent,
116
+ buildCloudMemorySection,
114
117
  isNestedInGitRepo,
115
118
  materializeWorkspace,
119
+ SERVER_TOOLS_MCP_SERVER_NAME,
116
120
  writeWorkspaceFiles,
117
121
  } from "./workspace.js";
118
122
 
@@ -237,6 +241,12 @@ export interface EngineResolvedConnection {
237
241
  accountServersFilter?: readonly string[];
238
242
  }
239
243
 
244
+ interface ActiveToolContext {
245
+ record: SessionRecord;
246
+ turnId: string;
247
+ emit: (payload: SessionEventPayload) => void;
248
+ }
249
+
240
250
  export interface EngineSendOptions extends SendMessageOptions {
241
251
  mode?: "chat" | "task";
242
252
  }
@@ -286,6 +296,8 @@ export class SessionEngine {
286
296
  /** Sessions with a turn being admitted (before the run promise exists). */
287
297
  private readonly pendingTurns = new Set<string>();
288
298
  private readonly activeTurns = new Map<string, Promise<TurnOutcome>>();
299
+ /** Context used by the cloud turn's HTTP MCP callbacks. */
300
+ private readonly activeToolContexts = new Map<string, ActiveToolContext>();
289
301
  /** Aborts the in-flight turn per session so a follow-up can preempt it. */
290
302
  private readonly turnAborts = new Map<string, AbortController>();
291
303
  /** Serializes turn admission per session (latest follow-up wins). */
@@ -318,6 +330,8 @@ export class SessionEngine {
318
330
  private readonly artifactsStore: ArtifactsStore;
319
331
  /** Serve-time resolved connections (see {@link setResolvedConnections}). */
320
332
  private resolvedConnections: EngineResolvedConnection[] = [];
333
+ /** Direct server-tool HTTP MCP transport exposed only to cloud turns. */
334
+ private cloudServerToolsConnection: ConnectionTransport | undefined;
321
335
  private readonly getCloudUsage: (
322
336
  sdkAgentId: string
323
337
  ) => Promise<CloudAgentUsage | undefined>;
@@ -369,7 +383,7 @@ export class SessionEngine {
369
383
  });
370
384
  this.artifactsStore = new ArtifactsStore({
371
385
  stateRoot: options.stateRoot,
372
- storage: this.storage,
386
+ agentName: options.project.name,
373
387
  config: options.project.artifacts,
374
388
  });
375
389
  this.abCollector = new ABCollector(
@@ -427,6 +441,10 @@ export class SessionEngine {
427
441
  }
428
442
  }
429
443
 
444
+ setCloudServerToolsConnection(connection: ConnectionTransport): void {
445
+ this.cloudServerToolsConnection = connection;
446
+ }
447
+
430
448
  /** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / Slack nudges). */
431
449
  get host(): HostContext {
432
450
  return {
@@ -1317,7 +1335,12 @@ export class SessionEngine {
1317
1335
  },
1318
1336
  signal
1319
1337
  );
1320
- outcome = await this.runner.runTurn(request);
1338
+ this.activeToolContexts.set(sessionId, { record, turnId, emit });
1339
+ try {
1340
+ outcome = await this.runner.runTurn(request);
1341
+ } finally {
1342
+ this.activeToolContexts.delete(sessionId);
1343
+ }
1321
1344
  } catch (error) {
1322
1345
  this.approvals.cancelSession(sessionId, "deny");
1323
1346
  outcome = { status: "error", errorMessage: describeError(error) };
@@ -1441,9 +1464,23 @@ export class SessionEngine {
1441
1464
  if (isFirstTurn) {
1442
1465
  if (runtime === "cloud") {
1443
1466
  // Cloud VMs have no materialized workspace: ship the full context,
1444
- // including agent tool script bodies.
1445
- const preamble = buildAgentsMdContent(agent, { includeScripts: true });
1446
- turnPrompt = `${preamble}\n\n${turnPrompt}`;
1467
+ // including agent tool script bodies. On hosted deployments the
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
+ });
1480
+ const memorySection = isCursorHostedFilesAvailable()
1481
+ ? `\n\n${buildCloudMemorySection(this.project.name)}`
1482
+ : "";
1483
+ turnPrompt = `${preamble}${memorySection}\n\n${turnPrompt}`;
1447
1484
  } else if (instructionsInvisibleToHarness) {
1448
1485
  // Local workspace whose AGENTS.md the harness will not load. With a
1449
1486
  // borrowed harness cwd, agent tool scripts live only in the durable
@@ -1469,7 +1506,7 @@ export class SessionEngine {
1469
1506
  ...(mergedCloud === undefined ? {} : { cloud: mergedCloud }),
1470
1507
  customTools:
1471
1508
  runtime === "local" ? this.buildCustomTools(record, turnId, emit) : {},
1472
- mcpServers: this.buildMcpServers(runtime),
1509
+ mcpServers: this.buildMcpServers(runtime, record.sessionId),
1473
1510
  subagents: buildRunnerSubagents(agent),
1474
1511
  onUpdate,
1475
1512
  signal,
@@ -1500,23 +1537,107 @@ export class SessionEngine {
1500
1537
  }
1501
1538
 
1502
1539
  /**
1503
- * Authored + resolved MCP servers for a turn; skips `hostOnly`, and skips
1504
- * host-OAuth connections on cloud turns (their tokens live on this host).
1540
+ * Connection names a cloud turn can only reach through the host connection
1541
+ * bridge on the tool MCP endpoint: filtered account connections without a
1542
+ * public bridge URL, and host-OAuth connections whose tokens live on this
1543
+ * host. `hostOnly` names are never bridged — that flag means the model
1544
+ * must not see the connection at all, on any runtime.
1545
+ */
1546
+ cloudBridgeableConnectionNames(): string[] {
1547
+ const hostOnlyNames = new Set(
1548
+ this.project.agent.connections
1549
+ .filter((connection) => connection.hostOnly === true)
1550
+ .map((connection) => connection.name)
1551
+ );
1552
+ const names: string[] = [];
1553
+ for (const connection of this.project.agent.connections) {
1554
+ if (
1555
+ !hostOnlyNames.has(connection.name) &&
1556
+ isHostOauthConnectionTransport(connection.transport)
1557
+ ) {
1558
+ names.push(connection.name);
1559
+ }
1560
+ }
1561
+ for (const resolved of this.resolvedConnections) {
1562
+ if (
1563
+ !hostOnlyNames.has(resolved.name) &&
1564
+ resolved.accountServersFilter !== undefined &&
1565
+ resolved.cloudUrl === undefined
1566
+ ) {
1567
+ names.push(resolved.name);
1568
+ }
1569
+ }
1570
+ return names;
1571
+ }
1572
+
1573
+ /**
1574
+ * True when cloud turns reach `name` through the host connection bridge —
1575
+ * the gate the tool MCP endpoint applies before forwarding a bridge
1576
+ * request to `host.mcp`, so `hostOnly` connections (and arbitrary names)
1577
+ * stay unreachable from a VM.
1578
+ */
1579
+ isCloudBridgedConnection(name: string): boolean {
1580
+ if (this.cloudServerToolsConnection === undefined) {
1581
+ return false;
1582
+ }
1583
+ return this.cloudBridgeableConnectionNames().includes(name);
1584
+ }
1585
+
1586
+ /**
1587
+ * Session-bound transport for one host-bridged connection: the tool MCP
1588
+ * endpoint with the connection named, so `tools/list` / `tools/call`
1589
+ * forward to the host-side client (`host.mcp`) where the credential —
1590
+ * account bridge secret or host OAuth token — actually lives.
1591
+ */
1592
+ private cloudBridgeTransport(
1593
+ sessionId: string,
1594
+ connectionName: string
1595
+ ): ConnectionTransport | undefined {
1596
+ const connection = this.cloudServerToolsConnection;
1597
+ if (connection === undefined || !("url" in connection)) {
1598
+ return undefined;
1599
+ }
1600
+ const url = new URL(connection.url);
1601
+ url.searchParams.set("agentkitSessionId", sessionId);
1602
+ url.searchParams.set("connection", connectionName);
1603
+ return { ...connection, url: url.toString() };
1604
+ }
1605
+
1606
+ /**
1607
+ * Authored + resolved MCP servers for a turn. Skips `hostOnly`. On cloud
1608
+ * turns, connections whose credential lives on this host — filtered
1609
+ * account connections without a public bridge URL, host-OAuth servers —
1610
+ * mount as host connection bridges over the tool MCP endpoint instead of
1611
+ * being omitted or failing the turn closed.
1505
1612
  */
1506
1613
  private buildMcpServers(
1507
- runtime: "local" | "cloud"
1614
+ runtime: "local" | "cloud",
1615
+ sessionId?: string
1508
1616
  ): Record<string, ConnectionTransport> {
1509
1617
  const hostOnlyNames = new Set(
1510
1618
  this.project.agent.connections
1511
1619
  .filter((connection) => connection.hostOnly === true)
1512
1620
  .map((connection) => connection.name)
1513
1621
  );
1622
+ const bridgedNames = new Set(
1623
+ runtime === "cloud" &&
1624
+ sessionId !== undefined &&
1625
+ this.cloudServerToolsConnection !== undefined &&
1626
+ "url" in this.cloudServerToolsConnection
1627
+ ? this.cloudBridgeableConnectionNames()
1628
+ : []
1629
+ );
1514
1630
  assertCloudCanHonorAccountServersFilters({
1515
1631
  runtime,
1516
1632
  resolvedConnections: this.resolvedConnections.filter(
1517
1633
  (resolved) => !hostOnlyNames.has(resolved.name)
1518
1634
  ),
1635
+ bridgedNames,
1519
1636
  });
1637
+ const bridgeTransport = (name: string): ConnectionTransport | undefined =>
1638
+ sessionId === undefined || !bridgedNames.has(name)
1639
+ ? undefined
1640
+ : this.cloudBridgeTransport(sessionId, name);
1520
1641
  const servers: Record<string, ConnectionTransport> = {};
1521
1642
  for (const connection of this.project.agent.connections) {
1522
1643
  if (hostOnlyNames.has(connection.name)) {
@@ -1527,12 +1648,17 @@ export class SessionEngine {
1527
1648
  }
1528
1649
  // A cloud VM cannot use a credential that lives on this host, and the
1529
1650
  // cloud API rejects the `oauth` key outright ("Unrecognized key(s) in
1530
- // object: 'oauth'"), which fails the whole turn. Omit it there; host
1531
- // tools (`ctx.host.mcp`) still reach the server over the host client.
1651
+ // object: 'oauth'"), which fails the whole turn. Mount the host
1652
+ // connection bridge instead when serve has a cloud tools URL; omit
1653
+ // otherwise (host tools via `ctx.host.mcp` still reach the server).
1532
1654
  if (
1533
1655
  runtime === "cloud" &&
1534
1656
  isHostOauthConnectionTransport(connection.transport)
1535
1657
  ) {
1658
+ const bridged = bridgeTransport(connection.name);
1659
+ if (bridged !== undefined) {
1660
+ servers[connection.name] = bridged;
1661
+ }
1536
1662
  continue;
1537
1663
  }
1538
1664
  servers[connection.name] = connection.transport;
@@ -1543,6 +1669,13 @@ export class SessionEngine {
1543
1669
  }
1544
1670
  const url = runtime === "cloud" ? resolved.cloudUrl : resolved.localUrl;
1545
1671
  if (url === undefined) {
1672
+ // Filtered account connections bridge through the host so the
1673
+ // `servers` filter is honored; unfiltered ones keep their documented
1674
+ // fallback to the account's native connectors on the VM.
1675
+ const bridged = bridgeTransport(resolved.name);
1676
+ if (bridged !== undefined) {
1677
+ servers[resolved.name] = bridged;
1678
+ }
1546
1679
  continue;
1547
1680
  }
1548
1681
  servers[resolved.name] = {
@@ -1550,6 +1683,27 @@ export class SessionEngine {
1550
1683
  headers: resolved.headers,
1551
1684
  };
1552
1685
  }
1686
+ if (
1687
+ runtime === "cloud" &&
1688
+ sessionId !== undefined &&
1689
+ this.cloudServerToolsConnection !== undefined &&
1690
+ this.project.agent.tools.some((tool) => tool.execution === "server")
1691
+ ) {
1692
+ if (servers[SERVER_TOOLS_MCP_SERVER_NAME] !== undefined) {
1693
+ throw new Error(
1694
+ `MCP connection name "${SERVER_TOOLS_MCP_SERVER_NAME}" is reserved for authored server tools on cloud turns.`
1695
+ );
1696
+ }
1697
+ const connection = this.cloudServerToolsConnection;
1698
+ if ("url" in connection) {
1699
+ const url = new URL(connection.url);
1700
+ url.searchParams.set("agentkitSessionId", sessionId);
1701
+ servers[SERVER_TOOLS_MCP_SERVER_NAME] = {
1702
+ ...connection,
1703
+ url: url.toString(),
1704
+ };
1705
+ }
1706
+ }
1553
1707
  return servers;
1554
1708
  }
1555
1709
 
@@ -1571,96 +1725,144 @@ export class SessionEngine {
1571
1725
  description: tool.description,
1572
1726
  inputSchema: tool.inputSchema as SDKCustomTool["inputSchema"],
1573
1727
  execute: async (args, context) => {
1574
- const validation = validateToolInput(
1575
- tool.definition.inputSchema,
1576
- args
1577
- );
1578
- if (!validation.ok) {
1579
- return {
1580
- content: [
1581
- {
1582
- type: "text",
1583
- text: `Invalid input for tool "${tool.name}": ${validation.message}`,
1584
- },
1585
- ],
1586
- isError: true,
1587
- };
1588
- }
1589
- const toolCallId = context.toolCallId ?? newToolCallId(tool.name);
1590
- const toolCtx: ToolContext = {
1591
- toolCallId,
1592
- session: this.sessionInfo(record),
1593
- workspaceDir: this.harnessCwd(record),
1594
- stateRoot: this.stateRoot,
1595
- host: this.hostForSession(record.sessionId),
1596
- artifacts: this.artifactsApi({
1597
- sessionId: record.sessionId,
1598
- turnId,
1599
- }),
1600
- send: (channelId, message, sendOptions = {}) =>
1601
- this.send(channelId, message, {
1602
- ...sendOptions,
1603
- auth:
1604
- sendOptions.auth !== undefined
1605
- ? sendOptions.auth
1606
- : record.auth,
1607
- }),
1608
- getSession: (channelId, sessionId) =>
1609
- this.getChannelSession(channelId, sessionId),
1610
- };
1611
- try {
1612
- const needsGate = await evaluateNeedsApproval(
1613
- tool.definition.needsApproval,
1614
- validation.value
1615
- );
1616
- if (needsGate) {
1617
- const decision = await this.parkForApproval({
1618
- sessionId: record.sessionId,
1619
- turnId,
1620
- callId: toolCallId,
1621
- toolName: tool.name,
1622
- args: validation.value,
1623
- emit,
1624
- });
1625
- if (decision === "deny") {
1626
- return {
1627
- content: [
1628
- {
1629
- type: "text",
1630
- text: `Tool "${tool.name}" was denied by a human approver.`,
1631
- },
1632
- ],
1633
- isError: true,
1634
- };
1635
- }
1636
- }
1637
- const started = Date.now();
1638
- this.logger(`[session] tool ${tool.name} start`);
1639
- const result = await execute(validation.value as never, toolCtx);
1640
- this.logger(
1641
- `[session] tool ${tool.name} done (${Date.now() - started}ms)`
1642
- );
1643
- return normalizeToolResult(result);
1644
- } catch (error) {
1645
- this.logger(
1646
- `[session] tool ${tool.name} failed: ${describeError(error)}`
1647
- );
1648
- return {
1649
- content: [
1650
- {
1651
- type: "text",
1652
- text: `Tool "${tool.name}" failed: ${describeError(error)}`,
1653
- },
1654
- ],
1655
- isError: true,
1656
- };
1657
- }
1728
+ return this.executeServerTool({
1729
+ tool,
1730
+ input: args,
1731
+ toolCallId: context.toolCallId,
1732
+ record,
1733
+ turnId,
1734
+ emit,
1735
+ });
1658
1736
  },
1659
1737
  };
1660
1738
  }
1661
1739
  return tools;
1662
1740
  }
1663
1741
 
1742
+ /**
1743
+ * True while `sessionId` has a turn in flight. The host connection bridge
1744
+ * applies this before forwarding to `host.mcp` — the same window
1745
+ * {@link callActiveServerTool} enforces for authored server tools — so an
1746
+ * endpoint credential that outlives a turn (e.g. in a VM's MCP config)
1747
+ * cannot reach host-credentialed connectors between turns.
1748
+ */
1749
+ hasActiveTurn(sessionId: string): boolean {
1750
+ return this.activeToolContexts.has(sessionId);
1751
+ }
1752
+
1753
+ async callActiveServerTool(
1754
+ sessionId: string,
1755
+ toolName: string,
1756
+ input: unknown
1757
+ ): Promise<SDKCustomToolResult> {
1758
+ const active = this.activeToolContexts.get(sessionId);
1759
+ if (active === undefined) {
1760
+ return toolErrorResult(
1761
+ `Session ${sessionId} has no active turn for tool "${toolName}".`
1762
+ );
1763
+ }
1764
+ const tool = this.project.agent.tools.find(
1765
+ (candidate) =>
1766
+ candidate.name === toolName && candidate.execution === "server"
1767
+ );
1768
+ if (tool === undefined) {
1769
+ return toolErrorResult(`Unknown server tool "${toolName}".`);
1770
+ }
1771
+ const toolCallId = newToolCallId(toolName);
1772
+ this.logger(
1773
+ `[session] cloud HTTP MCP tool ${toolName} dispatch (${sessionId}, ${toolCallId})`
1774
+ );
1775
+ const result = await this.executeServerTool({
1776
+ tool,
1777
+ input,
1778
+ toolCallId,
1779
+ record: active.record,
1780
+ turnId: active.turnId,
1781
+ emit: active.emit,
1782
+ });
1783
+ this.logger(
1784
+ `[session] cloud HTTP MCP tool ${toolName} complete (${sessionId}, ${toolCallId}, isError=${isErrorEnvelope(result)})`
1785
+ );
1786
+ return result;
1787
+ }
1788
+
1789
+ private async executeServerTool(args: {
1790
+ tool: ResolvedAgent["tools"][number];
1791
+ input: unknown;
1792
+ toolCallId?: string;
1793
+ record: SessionRecord;
1794
+ turnId: string;
1795
+ emit: (payload: SessionEventPayload) => void;
1796
+ }): Promise<SDKCustomToolResult> {
1797
+ const execute = args.tool.definition.execute;
1798
+ if (args.tool.execution !== "server" || typeof execute !== "function") {
1799
+ return toolErrorResult(`Tool "${args.tool.name}" is not a server tool.`);
1800
+ }
1801
+ const validation = validateToolInput(
1802
+ args.tool.definition.inputSchema,
1803
+ args.input
1804
+ );
1805
+ if (!validation.ok) {
1806
+ return toolErrorResult(
1807
+ `Invalid input for tool "${args.tool.name}": ${validation.message}`
1808
+ );
1809
+ }
1810
+ const toolCallId = args.toolCallId ?? newToolCallId(args.tool.name);
1811
+ const toolCtx: ToolContext = {
1812
+ toolCallId,
1813
+ session: this.sessionInfo(args.record),
1814
+ workspaceDir: this.harnessCwd(args.record),
1815
+ stateRoot: this.stateRoot,
1816
+ host: this.hostForSession(args.record.sessionId),
1817
+ artifacts: this.artifactsApi({
1818
+ sessionId: args.record.sessionId,
1819
+ turnId: args.turnId,
1820
+ }),
1821
+ send: (channelId, message, sendOptions = {}) =>
1822
+ this.send(channelId, message, {
1823
+ ...sendOptions,
1824
+ auth: sendOptions.auth ?? args.record.auth,
1825
+ }),
1826
+ getSession: (channelId, sessionId) =>
1827
+ this.getChannelSession(channelId, sessionId),
1828
+ };
1829
+ try {
1830
+ const needsGate = await evaluateNeedsApproval(
1831
+ args.tool.definition.needsApproval,
1832
+ validation.value
1833
+ );
1834
+ if (needsGate) {
1835
+ const decision = await this.parkForApproval({
1836
+ sessionId: args.record.sessionId,
1837
+ turnId: args.turnId,
1838
+ callId: toolCallId,
1839
+ toolName: args.tool.name,
1840
+ args: validation.value,
1841
+ emit: args.emit,
1842
+ });
1843
+ if (decision === "deny") {
1844
+ return toolErrorResult(
1845
+ `Tool "${args.tool.name}" was denied by a human approver.`
1846
+ );
1847
+ }
1848
+ }
1849
+ const started = Date.now();
1850
+ this.logger(`[session] tool ${args.tool.name} start`);
1851
+ const result = await execute(validation.value as never, toolCtx);
1852
+ this.logger(
1853
+ `[session] tool ${args.tool.name} done (${Date.now() - started}ms)`
1854
+ );
1855
+ return normalizeToolResult(result);
1856
+ } catch (error) {
1857
+ this.logger(
1858
+ `[session] tool ${args.tool.name} failed: ${describeError(error)}`
1859
+ );
1860
+ return toolErrorResult(
1861
+ `Tool "${args.tool.name}" failed: ${describeError(error)}`
1862
+ );
1863
+ }
1864
+ }
1865
+
1664
1866
  /**
1665
1867
  * Park a server-tool call until a human approves or denies. Emits
1666
1868
  * `action.approval_requested` / `action.approval_resolved` on the
@@ -2166,15 +2368,48 @@ export class SessionEngine {
2166
2368
  if (options?.includeAll === true) {
2167
2369
  return records;
2168
2370
  }
2371
+ const owned = await this.ownedSessionIds(caller);
2372
+ return records.filter(
2373
+ (record) => record.sessionId !== undefined && owned.has(record.sessionId)
2374
+ );
2375
+ }
2376
+
2377
+ /**
2378
+ * One artifact's file/blob payload for `GET /v1/artifacts/:id/content`.
2379
+ * Same visibility rule as {@link listArtifacts}. Undefined when the
2380
+ * artifact is missing, invisible to the caller, or carries no payload.
2381
+ */
2382
+ async getArtifactContent(
2383
+ caller: AuthContext | null,
2384
+ id: string,
2385
+ options?: { includeAll?: boolean }
2386
+ ): Promise<{ record: ArtifactRecord; body: Uint8Array } | undefined> {
2387
+ const record = await this.artifactsStore.get(id);
2388
+ if (record === undefined || record.content === undefined) {
2389
+ return undefined;
2390
+ }
2391
+ if (options?.includeAll !== true) {
2392
+ if (record.sessionId === undefined) {
2393
+ return undefined;
2394
+ }
2395
+ const owned = await this.ownedSessionIds(caller);
2396
+ if (!owned.has(record.sessionId)) {
2397
+ return undefined;
2398
+ }
2399
+ }
2400
+ const body = await this.artifactsStore.getContent(id);
2401
+ return body === undefined ? undefined : { record, body };
2402
+ }
2403
+
2404
+ private async ownedSessionIds(
2405
+ caller: AuthContext | null
2406
+ ): Promise<Set<string>> {
2169
2407
  const sessions = await this.sessions.list();
2170
- const owned = new Set(
2408
+ return new Set(
2171
2409
  sessions
2172
2410
  .filter((record) => samePrincipal(record.auth, caller))
2173
2411
  .map((record) => record.sessionId)
2174
2412
  );
2175
- return records.filter(
2176
- (record) => record.sessionId !== undefined && owned.has(record.sessionId)
2177
- );
2178
2413
  }
2179
2414
 
2180
2415
  /**
@@ -2715,7 +2950,7 @@ export class SessionEngine {
2715
2950
  }
2716
2951
 
2717
2952
  private async runBulkRestore(
2718
- storage: StorageCoordinator,
2953
+ storage: FrameworkSessionStore,
2719
2954
  policy: { maxSessions: number; maxAgeMs: number; maxTotalBytes: number }
2720
2955
  ): Promise<void> {
2721
2956
  const loaded = await storage.listSessions();
@@ -2876,7 +3111,7 @@ export class SessionEngine {
2876
3111
  }
2877
3112
 
2878
3113
  private async loadAndMaterializeByContinuation(
2879
- storage: StorageCoordinator,
3114
+ storage: FrameworkSessionStore,
2880
3115
  channelId: string,
2881
3116
  continuationKey: string
2882
3117
  ): Promise<SessionRecord | undefined> {
@@ -2892,7 +3127,7 @@ export class SessionEngine {
2892
3127
  }
2893
3128
 
2894
3129
  private async loadAndMaterializeById(
2895
- storage: StorageCoordinator,
3130
+ storage: FrameworkSessionStore,
2896
3131
  sessionId: string
2897
3132
  ): Promise<SessionRecord | undefined> {
2898
3133
  const record = await storage.getSessionById(sessionId);
@@ -2998,6 +3233,13 @@ function describeError(error: unknown): string {
2998
3233
  return error instanceof Error ? error.message : String(error);
2999
3234
  }
3000
3235
 
3236
+ function toolErrorResult(message: string): SDKCustomToolResult {
3237
+ return {
3238
+ content: [{ type: "text", text: message }],
3239
+ isError: true,
3240
+ };
3241
+ }
3242
+
3001
3243
  async function defaultGetCloudUsage(
3002
3244
  sdkAgentId: string
3003
3245
  ): Promise<CloudAgentUsage | undefined> {