@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.
- package/README.md +1 -1
- package/dist/artifacts.d.ts.map +1 -1
- package/dist/artifacts.js +0 -5
- package/dist/bin/agent-serve.d.ts +1 -0
- package/dist/bin/agent-serve.d.ts.map +1 -1
- package/dist/bin/agent-serve.js +32 -2
- package/dist/channels/github/types.d.ts +10 -7
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.CgZqLyWL.js → app.D5Mv1T0U.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.Cu7b6o1D.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.C4KigoQn.js → VPLocalSearchBox.CMq_BQce.js} +1 -1
- package/dist/docs/assets/chunks/{theme.C_Ty4yT7.js → theme.C6D9UPLK.js} +2 -2
- package/dist/docs/assets/{example-agents_weather-agent.md.Dmrcphhl.js → example-agents_weather-agent.md.BADkPqxQ.js} +6 -4
- package/dist/docs/assets/example-agents_weather-agent.md.BADkPqxQ.lean.js +1 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.js +9 -0
- package/dist/docs/assets/guides_cloud-runtime.md.CDJGvVC4.lean.js +1 -0
- package/dist/docs/building-with-agents.html +3 -3
- package/dist/docs/concepts.html +3 -3
- package/dist/docs/deployment.html +3 -3
- package/dist/docs/evals.html +3 -3
- package/dist/docs/example-agents/approval-buddy.html +3 -3
- package/dist/docs/example-agents/benny.html +3 -3
- package/dist/docs/example-agents/bugbot.html +3 -3
- package/dist/docs/example-agents/codebase-wiki.html +3 -3
- package/dist/docs/example-agents/codeowners-review.html +3 -3
- package/dist/docs/example-agents/concierge.html +3 -3
- package/dist/docs/example-agents/fsd.html +3 -3
- package/dist/docs/example-agents/index.html +3 -3
- package/dist/docs/example-agents/knowledge-base.html +3 -3
- package/dist/docs/example-agents/oncall.html +3 -3
- package/dist/docs/example-agents/security-reviewer.html +3 -3
- package/dist/docs/example-agents/slack-agent.html +3 -3
- package/dist/docs/example-agents/weather-agent.html +10 -8
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/files-backends/agent-store-presigned-url.d.ts +3 -11
- package/dist/files.d.ts +20 -0
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +20 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/internal/artifacts-store.d.ts +14 -6
- package/dist/internal/artifacts-store.d.ts.map +1 -1
- package/dist/internal/artifacts-store.js +160 -16
- package/dist/internal/cli-convert-automation.d.ts +39 -0
- package/dist/internal/cli-convert-automation.d.ts.map +1 -0
- package/dist/internal/cli-convert-automation.js +280 -0
- package/dist/internal/cli-deploy.d.ts +2 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +1 -1
- package/dist/internal/cli-mcp-oauth.d.ts +23 -0
- package/dist/internal/cli-mcp-oauth.d.ts.map +1 -1
- package/dist/internal/cli-mcp-oauth.js +57 -33
- package/dist/internal/convert-automation/convert-workflow.d.ts +10 -0
- package/dist/internal/convert-automation/convert-workflow.d.ts.map +1 -0
- package/dist/internal/convert-automation/convert-workflow.js +998 -0
- package/dist/internal/convert-automation/index.d.ts +3 -0
- package/dist/internal/convert-automation/index.d.ts.map +1 -0
- package/dist/internal/convert-automation/index.js +1 -0
- package/dist/internal/convert-automation/slug.d.ts +11 -0
- package/dist/internal/convert-automation/slug.d.ts.map +1 -0
- package/dist/internal/convert-automation/slug.js +36 -0
- package/dist/internal/convert-automation/types.d.ts +191 -0
- package/dist/internal/convert-automation/types.d.ts.map +1 -0
- package/dist/internal/convert-automation/types.js +13 -0
- package/dist/internal/deploy-client.d.ts +31 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +48 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +3 -32
- package/dist/internal/host-kv.d.ts +6 -2
- package/dist/internal/host-kv.d.ts.map +1 -1
- package/dist/internal/init-project.d.ts +1 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +1 -1
- package/dist/internal/mcp-endpoint.d.ts +6 -0
- package/dist/internal/mcp-endpoint.d.ts.map +1 -1
- package/dist/internal/mcp-endpoint.js +168 -0
- package/dist/internal/resolved-connections.d.ts +15 -0
- package/dist/internal/resolved-connections.d.ts.map +1 -1
- package/dist/internal/resolved-connections.js +12 -5
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +75 -7
- package/dist/internal/session-engine.d.ts +56 -3
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +262 -84
- package/dist/internal/storage-coordinator.d.ts +9 -16
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +7 -52
- package/dist/internal/storage-roles.d.ts +78 -0
- package/dist/internal/storage-roles.d.ts.map +1 -0
- package/dist/internal/storage-roles.js +24 -0
- package/dist/internal/workspace.d.ts +36 -5
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +66 -0
- package/dist/memory.d.ts +22 -6
- package/dist/memory.d.ts.map +1 -1
- package/dist/memory.js +100 -8
- package/dist/playground/assets/{index-CVlCAcUx.js → index-D7OV8B_H.js} +50 -50
- package/dist/playground/assets/index-DOb96C0M.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage.d.ts +0 -4
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +0 -4
- package/dist/types.d.ts +18 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/example-agents/weather-agent.md +29 -11
- package/docs/guides/cloud-runtime.md +6 -4
- package/package.json +3 -1
- package/src/artifacts.ts +0 -6
- package/src/bin/agent-serve.ts +36 -2
- package/src/channels/github/types.ts +10 -7
- package/src/files.ts +27 -0
- package/src/index.ts +6 -1
- package/src/internal/artifacts-store.ts +185 -22
- package/src/internal/cli-convert-automation.ts +352 -0
- package/src/internal/cli-deploy.ts +1 -1
- package/src/internal/cli-mcp-oauth.ts +69 -31
- package/src/internal/convert-automation/convert-workflow.ts +1422 -0
- package/src/internal/convert-automation/index.ts +10 -0
- package/src/internal/convert-automation/slug.ts +38 -0
- package/src/internal/convert-automation/types.ts +199 -0
- package/src/internal/deploy-client.ts +78 -0
- package/src/internal/discovery.ts +3 -37
- package/src/internal/host-kv.ts +6 -2
- package/src/internal/init-project.ts +1 -1
- package/src/internal/mcp-endpoint.ts +243 -0
- package/src/internal/resolved-connections.ts +32 -6
- package/src/internal/server.ts +99 -1
- package/src/internal/session-engine.ts +345 -103
- package/src/internal/storage-coordinator.ts +15 -63
- package/src/internal/storage-roles.ts +86 -0
- package/src/internal/workspace.ts +81 -6
- package/src/memory.ts +126 -10
- package/src/storage.ts +0 -6
- package/src/types.ts +15 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.B1uHGxBZ.js +0 -1
- package/dist/docs/assets/example-agents_weather-agent.md.Dmrcphhl.lean.js +0 -1
- package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.js +0 -9
- package/dist/docs/assets/guides_cloud-runtime.md.BZ2GA7Es.lean.js +0 -1
- 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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1446
|
-
|
|
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
|
-
*
|
|
1504
|
-
*
|
|
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.
|
|
1531
|
-
//
|
|
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
|
-
|
|
1575
|
-
tool
|
|
1576
|
-
args
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
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
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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> {
|