@cursor/july 0.1.33 → 0.1.34
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.FPupl4SP.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.WoYunhnT.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.C4KigoQn.js → VPLocalSearchBox.Cd182Cu0.js} +1 -1
- package/dist/docs/assets/chunks/{theme.C_Ty4yT7.js → theme.BEM3Okcd.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/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 +252 -83
- package/dist/internal/storage-coordinator.d.ts +0 -15
- package/dist/internal/storage-coordinator.d.ts.map +1 -1
- package/dist/internal/storage-coordinator.js +0 -52
- package/dist/internal/workspace.d.ts +10 -5
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +13 -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-CDDWw0YX.js} +46 -46
- package/dist/playground/assets/index-MVuNTd8v.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/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 +329 -99
- package/src/internal/storage-coordinator.ts +0 -62
- package/src/internal/workspace.ts +19 -5
- 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,
|
|
@@ -111,6 +112,7 @@ import {
|
|
|
111
112
|
import { TurnGovernor } from "./turn-governor.js";
|
|
112
113
|
import {
|
|
113
114
|
buildAgentsMdContent,
|
|
115
|
+
buildCloudMemorySection,
|
|
114
116
|
isNestedInGitRepo,
|
|
115
117
|
materializeWorkspace,
|
|
116
118
|
writeWorkspaceFiles,
|
|
@@ -237,6 +239,12 @@ export interface EngineResolvedConnection {
|
|
|
237
239
|
accountServersFilter?: readonly string[];
|
|
238
240
|
}
|
|
239
241
|
|
|
242
|
+
interface ActiveToolContext {
|
|
243
|
+
record: SessionRecord;
|
|
244
|
+
turnId: string;
|
|
245
|
+
emit: (payload: SessionEventPayload) => void;
|
|
246
|
+
}
|
|
247
|
+
|
|
240
248
|
export interface EngineSendOptions extends SendMessageOptions {
|
|
241
249
|
mode?: "chat" | "task";
|
|
242
250
|
}
|
|
@@ -286,6 +294,8 @@ export class SessionEngine {
|
|
|
286
294
|
/** Sessions with a turn being admitted (before the run promise exists). */
|
|
287
295
|
private readonly pendingTurns = new Set<string>();
|
|
288
296
|
private readonly activeTurns = new Map<string, Promise<TurnOutcome>>();
|
|
297
|
+
/** Context used by the cloud turn's HTTP MCP callbacks. */
|
|
298
|
+
private readonly activeToolContexts = new Map<string, ActiveToolContext>();
|
|
289
299
|
/** Aborts the in-flight turn per session so a follow-up can preempt it. */
|
|
290
300
|
private readonly turnAborts = new Map<string, AbortController>();
|
|
291
301
|
/** Serializes turn admission per session (latest follow-up wins). */
|
|
@@ -318,6 +328,8 @@ export class SessionEngine {
|
|
|
318
328
|
private readonly artifactsStore: ArtifactsStore;
|
|
319
329
|
/** Serve-time resolved connections (see {@link setResolvedConnections}). */
|
|
320
330
|
private resolvedConnections: EngineResolvedConnection[] = [];
|
|
331
|
+
/** Direct server-tool HTTP MCP transport exposed only to cloud turns. */
|
|
332
|
+
private cloudServerToolsConnection: ConnectionTransport | undefined;
|
|
321
333
|
private readonly getCloudUsage: (
|
|
322
334
|
sdkAgentId: string
|
|
323
335
|
) => Promise<CloudAgentUsage | undefined>;
|
|
@@ -369,7 +381,7 @@ export class SessionEngine {
|
|
|
369
381
|
});
|
|
370
382
|
this.artifactsStore = new ArtifactsStore({
|
|
371
383
|
stateRoot: options.stateRoot,
|
|
372
|
-
|
|
384
|
+
agentName: options.project.name,
|
|
373
385
|
config: options.project.artifacts,
|
|
374
386
|
});
|
|
375
387
|
this.abCollector = new ABCollector(
|
|
@@ -427,6 +439,10 @@ export class SessionEngine {
|
|
|
427
439
|
}
|
|
428
440
|
}
|
|
429
441
|
|
|
442
|
+
setCloudServerToolsConnection(connection: ConnectionTransport): void {
|
|
443
|
+
this.cloudServerToolsConnection = connection;
|
|
444
|
+
}
|
|
445
|
+
|
|
430
446
|
/** Shared host services (MCP / GitHub / Slack / reminders / evals / kv / Slack nudges). */
|
|
431
447
|
get host(): HostContext {
|
|
432
448
|
return {
|
|
@@ -1317,7 +1333,12 @@ export class SessionEngine {
|
|
|
1317
1333
|
},
|
|
1318
1334
|
signal
|
|
1319
1335
|
);
|
|
1320
|
-
|
|
1336
|
+
this.activeToolContexts.set(sessionId, { record, turnId, emit });
|
|
1337
|
+
try {
|
|
1338
|
+
outcome = await this.runner.runTurn(request);
|
|
1339
|
+
} finally {
|
|
1340
|
+
this.activeToolContexts.delete(sessionId);
|
|
1341
|
+
}
|
|
1321
1342
|
} catch (error) {
|
|
1322
1343
|
this.approvals.cancelSession(sessionId, "deny");
|
|
1323
1344
|
outcome = { status: "error", errorMessage: describeError(error) };
|
|
@@ -1441,9 +1462,13 @@ export class SessionEngine {
|
|
|
1441
1462
|
if (isFirstTurn) {
|
|
1442
1463
|
if (runtime === "cloud") {
|
|
1443
1464
|
// Cloud VMs have no materialized workspace: ship the full context,
|
|
1444
|
-
// including agent tool script bodies.
|
|
1465
|
+
// including agent tool script bodies. On hosted deployments the
|
|
1466
|
+
// shared memory journal is on the store mount — say where.
|
|
1445
1467
|
const preamble = buildAgentsMdContent(agent, { includeScripts: true });
|
|
1446
|
-
|
|
1468
|
+
const memorySection = isCursorHostedFilesAvailable()
|
|
1469
|
+
? `\n\n${buildCloudMemorySection(this.project.name)}`
|
|
1470
|
+
: "";
|
|
1471
|
+
turnPrompt = `${preamble}${memorySection}\n\n${turnPrompt}`;
|
|
1447
1472
|
} else if (instructionsInvisibleToHarness) {
|
|
1448
1473
|
// Local workspace whose AGENTS.md the harness will not load. With a
|
|
1449
1474
|
// borrowed harness cwd, agent tool scripts live only in the durable
|
|
@@ -1469,7 +1494,7 @@ export class SessionEngine {
|
|
|
1469
1494
|
...(mergedCloud === undefined ? {} : { cloud: mergedCloud }),
|
|
1470
1495
|
customTools:
|
|
1471
1496
|
runtime === "local" ? this.buildCustomTools(record, turnId, emit) : {},
|
|
1472
|
-
mcpServers: this.buildMcpServers(runtime),
|
|
1497
|
+
mcpServers: this.buildMcpServers(runtime, record.sessionId),
|
|
1473
1498
|
subagents: buildRunnerSubagents(agent),
|
|
1474
1499
|
onUpdate,
|
|
1475
1500
|
signal,
|
|
@@ -1500,23 +1525,107 @@ export class SessionEngine {
|
|
|
1500
1525
|
}
|
|
1501
1526
|
|
|
1502
1527
|
/**
|
|
1503
|
-
*
|
|
1504
|
-
*
|
|
1528
|
+
* Connection names a cloud turn can only reach through the host connection
|
|
1529
|
+
* bridge on the tool MCP endpoint: filtered account connections without a
|
|
1530
|
+
* public bridge URL, and host-OAuth connections whose tokens live on this
|
|
1531
|
+
* host. `hostOnly` names are never bridged — that flag means the model
|
|
1532
|
+
* must not see the connection at all, on any runtime.
|
|
1533
|
+
*/
|
|
1534
|
+
cloudBridgeableConnectionNames(): string[] {
|
|
1535
|
+
const hostOnlyNames = new Set(
|
|
1536
|
+
this.project.agent.connections
|
|
1537
|
+
.filter((connection) => connection.hostOnly === true)
|
|
1538
|
+
.map((connection) => connection.name)
|
|
1539
|
+
);
|
|
1540
|
+
const names: string[] = [];
|
|
1541
|
+
for (const connection of this.project.agent.connections) {
|
|
1542
|
+
if (
|
|
1543
|
+
!hostOnlyNames.has(connection.name) &&
|
|
1544
|
+
isHostOauthConnectionTransport(connection.transport)
|
|
1545
|
+
) {
|
|
1546
|
+
names.push(connection.name);
|
|
1547
|
+
}
|
|
1548
|
+
}
|
|
1549
|
+
for (const resolved of this.resolvedConnections) {
|
|
1550
|
+
if (
|
|
1551
|
+
!hostOnlyNames.has(resolved.name) &&
|
|
1552
|
+
resolved.accountServersFilter !== undefined &&
|
|
1553
|
+
resolved.cloudUrl === undefined
|
|
1554
|
+
) {
|
|
1555
|
+
names.push(resolved.name);
|
|
1556
|
+
}
|
|
1557
|
+
}
|
|
1558
|
+
return names;
|
|
1559
|
+
}
|
|
1560
|
+
|
|
1561
|
+
/**
|
|
1562
|
+
* True when cloud turns reach `name` through the host connection bridge —
|
|
1563
|
+
* the gate the tool MCP endpoint applies before forwarding a bridge
|
|
1564
|
+
* request to `host.mcp`, so `hostOnly` connections (and arbitrary names)
|
|
1565
|
+
* stay unreachable from a VM.
|
|
1566
|
+
*/
|
|
1567
|
+
isCloudBridgedConnection(name: string): boolean {
|
|
1568
|
+
if (this.cloudServerToolsConnection === undefined) {
|
|
1569
|
+
return false;
|
|
1570
|
+
}
|
|
1571
|
+
return this.cloudBridgeableConnectionNames().includes(name);
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* Session-bound transport for one host-bridged connection: the tool MCP
|
|
1576
|
+
* endpoint with the connection named, so `tools/list` / `tools/call`
|
|
1577
|
+
* forward to the host-side client (`host.mcp`) where the credential —
|
|
1578
|
+
* account bridge secret or host OAuth token — actually lives.
|
|
1579
|
+
*/
|
|
1580
|
+
private cloudBridgeTransport(
|
|
1581
|
+
sessionId: string,
|
|
1582
|
+
connectionName: string
|
|
1583
|
+
): ConnectionTransport | undefined {
|
|
1584
|
+
const connection = this.cloudServerToolsConnection;
|
|
1585
|
+
if (connection === undefined || !("url" in connection)) {
|
|
1586
|
+
return undefined;
|
|
1587
|
+
}
|
|
1588
|
+
const url = new URL(connection.url);
|
|
1589
|
+
url.searchParams.set("agentkitSessionId", sessionId);
|
|
1590
|
+
url.searchParams.set("connection", connectionName);
|
|
1591
|
+
return { ...connection, url: url.toString() };
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
/**
|
|
1595
|
+
* Authored + resolved MCP servers for a turn. Skips `hostOnly`. On cloud
|
|
1596
|
+
* turns, connections whose credential lives on this host — filtered
|
|
1597
|
+
* account connections without a public bridge URL, host-OAuth servers —
|
|
1598
|
+
* mount as host connection bridges over the tool MCP endpoint instead of
|
|
1599
|
+
* being omitted or failing the turn closed.
|
|
1505
1600
|
*/
|
|
1506
1601
|
private buildMcpServers(
|
|
1507
|
-
runtime: "local" | "cloud"
|
|
1602
|
+
runtime: "local" | "cloud",
|
|
1603
|
+
sessionId?: string
|
|
1508
1604
|
): Record<string, ConnectionTransport> {
|
|
1509
1605
|
const hostOnlyNames = new Set(
|
|
1510
1606
|
this.project.agent.connections
|
|
1511
1607
|
.filter((connection) => connection.hostOnly === true)
|
|
1512
1608
|
.map((connection) => connection.name)
|
|
1513
1609
|
);
|
|
1610
|
+
const bridgedNames = new Set(
|
|
1611
|
+
runtime === "cloud" &&
|
|
1612
|
+
sessionId !== undefined &&
|
|
1613
|
+
this.cloudServerToolsConnection !== undefined &&
|
|
1614
|
+
"url" in this.cloudServerToolsConnection
|
|
1615
|
+
? this.cloudBridgeableConnectionNames()
|
|
1616
|
+
: []
|
|
1617
|
+
);
|
|
1514
1618
|
assertCloudCanHonorAccountServersFilters({
|
|
1515
1619
|
runtime,
|
|
1516
1620
|
resolvedConnections: this.resolvedConnections.filter(
|
|
1517
1621
|
(resolved) => !hostOnlyNames.has(resolved.name)
|
|
1518
1622
|
),
|
|
1623
|
+
bridgedNames,
|
|
1519
1624
|
});
|
|
1625
|
+
const bridgeTransport = (name: string): ConnectionTransport | undefined =>
|
|
1626
|
+
sessionId === undefined || !bridgedNames.has(name)
|
|
1627
|
+
? undefined
|
|
1628
|
+
: this.cloudBridgeTransport(sessionId, name);
|
|
1520
1629
|
const servers: Record<string, ConnectionTransport> = {};
|
|
1521
1630
|
for (const connection of this.project.agent.connections) {
|
|
1522
1631
|
if (hostOnlyNames.has(connection.name)) {
|
|
@@ -1527,12 +1636,17 @@ export class SessionEngine {
|
|
|
1527
1636
|
}
|
|
1528
1637
|
// A cloud VM cannot use a credential that lives on this host, and the
|
|
1529
1638
|
// cloud API rejects the `oauth` key outright ("Unrecognized key(s) in
|
|
1530
|
-
// object: 'oauth'"), which fails the whole turn.
|
|
1531
|
-
//
|
|
1639
|
+
// object: 'oauth'"), which fails the whole turn. Mount the host
|
|
1640
|
+
// connection bridge instead when serve has a cloud tools URL; omit
|
|
1641
|
+
// otherwise (host tools via `ctx.host.mcp` still reach the server).
|
|
1532
1642
|
if (
|
|
1533
1643
|
runtime === "cloud" &&
|
|
1534
1644
|
isHostOauthConnectionTransport(connection.transport)
|
|
1535
1645
|
) {
|
|
1646
|
+
const bridged = bridgeTransport(connection.name);
|
|
1647
|
+
if (bridged !== undefined) {
|
|
1648
|
+
servers[connection.name] = bridged;
|
|
1649
|
+
}
|
|
1536
1650
|
continue;
|
|
1537
1651
|
}
|
|
1538
1652
|
servers[connection.name] = connection.transport;
|
|
@@ -1543,6 +1657,13 @@ export class SessionEngine {
|
|
|
1543
1657
|
}
|
|
1544
1658
|
const url = runtime === "cloud" ? resolved.cloudUrl : resolved.localUrl;
|
|
1545
1659
|
if (url === undefined) {
|
|
1660
|
+
// Filtered account connections bridge through the host so the
|
|
1661
|
+
// `servers` filter is honored; unfiltered ones keep their documented
|
|
1662
|
+
// fallback to the account's native connectors on the VM.
|
|
1663
|
+
const bridged = bridgeTransport(resolved.name);
|
|
1664
|
+
if (bridged !== undefined) {
|
|
1665
|
+
servers[resolved.name] = bridged;
|
|
1666
|
+
}
|
|
1546
1667
|
continue;
|
|
1547
1668
|
}
|
|
1548
1669
|
servers[resolved.name] = {
|
|
@@ -1550,6 +1671,27 @@ export class SessionEngine {
|
|
|
1550
1671
|
headers: resolved.headers,
|
|
1551
1672
|
};
|
|
1552
1673
|
}
|
|
1674
|
+
if (
|
|
1675
|
+
runtime === "cloud" &&
|
|
1676
|
+
sessionId !== undefined &&
|
|
1677
|
+
this.cloudServerToolsConnection !== undefined &&
|
|
1678
|
+
this.project.agent.tools.some((tool) => tool.execution === "server")
|
|
1679
|
+
) {
|
|
1680
|
+
if (servers["agentsdk-tools"] !== undefined) {
|
|
1681
|
+
throw new Error(
|
|
1682
|
+
'MCP connection name "agentsdk-tools" is reserved for authored server tools on cloud turns.'
|
|
1683
|
+
);
|
|
1684
|
+
}
|
|
1685
|
+
const connection = this.cloudServerToolsConnection;
|
|
1686
|
+
if ("url" in connection) {
|
|
1687
|
+
const url = new URL(connection.url);
|
|
1688
|
+
url.searchParams.set("agentkitSessionId", sessionId);
|
|
1689
|
+
servers["agentsdk-tools"] = {
|
|
1690
|
+
...connection,
|
|
1691
|
+
url: url.toString(),
|
|
1692
|
+
};
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1553
1695
|
return servers;
|
|
1554
1696
|
}
|
|
1555
1697
|
|
|
@@ -1571,96 +1713,144 @@ export class SessionEngine {
|
|
|
1571
1713
|
description: tool.description,
|
|
1572
1714
|
inputSchema: tool.inputSchema as SDKCustomTool["inputSchema"],
|
|
1573
1715
|
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
|
-
}
|
|
1716
|
+
return this.executeServerTool({
|
|
1717
|
+
tool,
|
|
1718
|
+
input: args,
|
|
1719
|
+
toolCallId: context.toolCallId,
|
|
1720
|
+
record,
|
|
1721
|
+
turnId,
|
|
1722
|
+
emit,
|
|
1723
|
+
});
|
|
1658
1724
|
},
|
|
1659
1725
|
};
|
|
1660
1726
|
}
|
|
1661
1727
|
return tools;
|
|
1662
1728
|
}
|
|
1663
1729
|
|
|
1730
|
+
/**
|
|
1731
|
+
* True while `sessionId` has a turn in flight. The host connection bridge
|
|
1732
|
+
* applies this before forwarding to `host.mcp` — the same window
|
|
1733
|
+
* {@link callActiveServerTool} enforces for authored server tools — so an
|
|
1734
|
+
* endpoint credential that outlives a turn (e.g. in a VM's MCP config)
|
|
1735
|
+
* cannot reach host-credentialed connectors between turns.
|
|
1736
|
+
*/
|
|
1737
|
+
hasActiveTurn(sessionId: string): boolean {
|
|
1738
|
+
return this.activeToolContexts.has(sessionId);
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
async callActiveServerTool(
|
|
1742
|
+
sessionId: string,
|
|
1743
|
+
toolName: string,
|
|
1744
|
+
input: unknown
|
|
1745
|
+
): Promise<SDKCustomToolResult> {
|
|
1746
|
+
const active = this.activeToolContexts.get(sessionId);
|
|
1747
|
+
if (active === undefined) {
|
|
1748
|
+
return toolErrorResult(
|
|
1749
|
+
`Session ${sessionId} has no active turn for tool "${toolName}".`
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
const tool = this.project.agent.tools.find(
|
|
1753
|
+
(candidate) =>
|
|
1754
|
+
candidate.name === toolName && candidate.execution === "server"
|
|
1755
|
+
);
|
|
1756
|
+
if (tool === undefined) {
|
|
1757
|
+
return toolErrorResult(`Unknown server tool "${toolName}".`);
|
|
1758
|
+
}
|
|
1759
|
+
const toolCallId = newToolCallId(toolName);
|
|
1760
|
+
this.logger(
|
|
1761
|
+
`[session] cloud HTTP MCP tool ${toolName} dispatch (${sessionId}, ${toolCallId})`
|
|
1762
|
+
);
|
|
1763
|
+
const result = await this.executeServerTool({
|
|
1764
|
+
tool,
|
|
1765
|
+
input,
|
|
1766
|
+
toolCallId,
|
|
1767
|
+
record: active.record,
|
|
1768
|
+
turnId: active.turnId,
|
|
1769
|
+
emit: active.emit,
|
|
1770
|
+
});
|
|
1771
|
+
this.logger(
|
|
1772
|
+
`[session] cloud HTTP MCP tool ${toolName} complete (${sessionId}, ${toolCallId}, isError=${isErrorEnvelope(result)})`
|
|
1773
|
+
);
|
|
1774
|
+
return result;
|
|
1775
|
+
}
|
|
1776
|
+
|
|
1777
|
+
private async executeServerTool(args: {
|
|
1778
|
+
tool: ResolvedAgent["tools"][number];
|
|
1779
|
+
input: unknown;
|
|
1780
|
+
toolCallId?: string;
|
|
1781
|
+
record: SessionRecord;
|
|
1782
|
+
turnId: string;
|
|
1783
|
+
emit: (payload: SessionEventPayload) => void;
|
|
1784
|
+
}): Promise<SDKCustomToolResult> {
|
|
1785
|
+
const execute = args.tool.definition.execute;
|
|
1786
|
+
if (args.tool.execution !== "server" || typeof execute !== "function") {
|
|
1787
|
+
return toolErrorResult(`Tool "${args.tool.name}" is not a server tool.`);
|
|
1788
|
+
}
|
|
1789
|
+
const validation = validateToolInput(
|
|
1790
|
+
args.tool.definition.inputSchema,
|
|
1791
|
+
args.input
|
|
1792
|
+
);
|
|
1793
|
+
if (!validation.ok) {
|
|
1794
|
+
return toolErrorResult(
|
|
1795
|
+
`Invalid input for tool "${args.tool.name}": ${validation.message}`
|
|
1796
|
+
);
|
|
1797
|
+
}
|
|
1798
|
+
const toolCallId = args.toolCallId ?? newToolCallId(args.tool.name);
|
|
1799
|
+
const toolCtx: ToolContext = {
|
|
1800
|
+
toolCallId,
|
|
1801
|
+
session: this.sessionInfo(args.record),
|
|
1802
|
+
workspaceDir: this.harnessCwd(args.record),
|
|
1803
|
+
stateRoot: this.stateRoot,
|
|
1804
|
+
host: this.hostForSession(args.record.sessionId),
|
|
1805
|
+
artifacts: this.artifactsApi({
|
|
1806
|
+
sessionId: args.record.sessionId,
|
|
1807
|
+
turnId: args.turnId,
|
|
1808
|
+
}),
|
|
1809
|
+
send: (channelId, message, sendOptions = {}) =>
|
|
1810
|
+
this.send(channelId, message, {
|
|
1811
|
+
...sendOptions,
|
|
1812
|
+
auth: sendOptions.auth ?? args.record.auth,
|
|
1813
|
+
}),
|
|
1814
|
+
getSession: (channelId, sessionId) =>
|
|
1815
|
+
this.getChannelSession(channelId, sessionId),
|
|
1816
|
+
};
|
|
1817
|
+
try {
|
|
1818
|
+
const needsGate = await evaluateNeedsApproval(
|
|
1819
|
+
args.tool.definition.needsApproval,
|
|
1820
|
+
validation.value
|
|
1821
|
+
);
|
|
1822
|
+
if (needsGate) {
|
|
1823
|
+
const decision = await this.parkForApproval({
|
|
1824
|
+
sessionId: args.record.sessionId,
|
|
1825
|
+
turnId: args.turnId,
|
|
1826
|
+
callId: toolCallId,
|
|
1827
|
+
toolName: args.tool.name,
|
|
1828
|
+
args: validation.value,
|
|
1829
|
+
emit: args.emit,
|
|
1830
|
+
});
|
|
1831
|
+
if (decision === "deny") {
|
|
1832
|
+
return toolErrorResult(
|
|
1833
|
+
`Tool "${args.tool.name}" was denied by a human approver.`
|
|
1834
|
+
);
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
const started = Date.now();
|
|
1838
|
+
this.logger(`[session] tool ${args.tool.name} start`);
|
|
1839
|
+
const result = await execute(validation.value as never, toolCtx);
|
|
1840
|
+
this.logger(
|
|
1841
|
+
`[session] tool ${args.tool.name} done (${Date.now() - started}ms)`
|
|
1842
|
+
);
|
|
1843
|
+
return normalizeToolResult(result);
|
|
1844
|
+
} catch (error) {
|
|
1845
|
+
this.logger(
|
|
1846
|
+
`[session] tool ${args.tool.name} failed: ${describeError(error)}`
|
|
1847
|
+
);
|
|
1848
|
+
return toolErrorResult(
|
|
1849
|
+
`Tool "${args.tool.name}" failed: ${describeError(error)}`
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1664
1854
|
/**
|
|
1665
1855
|
* Park a server-tool call until a human approves or denies. Emits
|
|
1666
1856
|
* `action.approval_requested` / `action.approval_resolved` on the
|
|
@@ -2166,15 +2356,48 @@ export class SessionEngine {
|
|
|
2166
2356
|
if (options?.includeAll === true) {
|
|
2167
2357
|
return records;
|
|
2168
2358
|
}
|
|
2359
|
+
const owned = await this.ownedSessionIds(caller);
|
|
2360
|
+
return records.filter(
|
|
2361
|
+
(record) => record.sessionId !== undefined && owned.has(record.sessionId)
|
|
2362
|
+
);
|
|
2363
|
+
}
|
|
2364
|
+
|
|
2365
|
+
/**
|
|
2366
|
+
* One artifact's file/blob payload for `GET /v1/artifacts/:id/content`.
|
|
2367
|
+
* Same visibility rule as {@link listArtifacts}. Undefined when the
|
|
2368
|
+
* artifact is missing, invisible to the caller, or carries no payload.
|
|
2369
|
+
*/
|
|
2370
|
+
async getArtifactContent(
|
|
2371
|
+
caller: AuthContext | null,
|
|
2372
|
+
id: string,
|
|
2373
|
+
options?: { includeAll?: boolean }
|
|
2374
|
+
): Promise<{ record: ArtifactRecord; body: Uint8Array } | undefined> {
|
|
2375
|
+
const record = await this.artifactsStore.get(id);
|
|
2376
|
+
if (record === undefined || record.content === undefined) {
|
|
2377
|
+
return undefined;
|
|
2378
|
+
}
|
|
2379
|
+
if (options?.includeAll !== true) {
|
|
2380
|
+
if (record.sessionId === undefined) {
|
|
2381
|
+
return undefined;
|
|
2382
|
+
}
|
|
2383
|
+
const owned = await this.ownedSessionIds(caller);
|
|
2384
|
+
if (!owned.has(record.sessionId)) {
|
|
2385
|
+
return undefined;
|
|
2386
|
+
}
|
|
2387
|
+
}
|
|
2388
|
+
const body = await this.artifactsStore.getContent(id);
|
|
2389
|
+
return body === undefined ? undefined : { record, body };
|
|
2390
|
+
}
|
|
2391
|
+
|
|
2392
|
+
private async ownedSessionIds(
|
|
2393
|
+
caller: AuthContext | null
|
|
2394
|
+
): Promise<Set<string>> {
|
|
2169
2395
|
const sessions = await this.sessions.list();
|
|
2170
|
-
|
|
2396
|
+
return new Set(
|
|
2171
2397
|
sessions
|
|
2172
2398
|
.filter((record) => samePrincipal(record.auth, caller))
|
|
2173
2399
|
.map((record) => record.sessionId)
|
|
2174
2400
|
);
|
|
2175
|
-
return records.filter(
|
|
2176
|
-
(record) => record.sessionId !== undefined && owned.has(record.sessionId)
|
|
2177
|
-
);
|
|
2178
2401
|
}
|
|
2179
2402
|
|
|
2180
2403
|
/**
|
|
@@ -2998,6 +3221,13 @@ function describeError(error: unknown): string {
|
|
|
2998
3221
|
return error instanceof Error ? error.message : String(error);
|
|
2999
3222
|
}
|
|
3000
3223
|
|
|
3224
|
+
function toolErrorResult(message: string): SDKCustomToolResult {
|
|
3225
|
+
return {
|
|
3226
|
+
content: [{ type: "text", text: message }],
|
|
3227
|
+
isError: true,
|
|
3228
|
+
};
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3001
3231
|
async function defaultGetCloudUsage(
|
|
3002
3232
|
sdkAgentId: string
|
|
3003
3233
|
): Promise<CloudAgentUsage | undefined> {
|
|
@@ -458,68 +458,6 @@ export class StorageCoordinator {
|
|
|
458
458
|
await this.definition.delete?.(key, this.context("policy"));
|
|
459
459
|
}
|
|
460
460
|
|
|
461
|
-
// ==========================================================================
|
|
462
|
-
// Artifacts (`ctx.artifacts`)
|
|
463
|
-
// ==========================================================================
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Whether the sink can serve awaited artifact rows: `get` (upsert
|
|
467
|
-
* read-back), `list` (listing + eviction), and `delete` (`max` cap
|
|
468
|
-
* eviction) are all required — without `delete` the retention cap
|
|
469
|
-
* could never evict, so rows fall back to the filesystem store.
|
|
470
|
-
*/
|
|
471
|
-
get supportsArtifacts(): boolean {
|
|
472
|
-
return (
|
|
473
|
-
this.definition.get !== undefined &&
|
|
474
|
-
this.definition.list !== undefined &&
|
|
475
|
-
this.definition.delete !== undefined
|
|
476
|
-
);
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
/** Awaited artifact-row read. Errors propagate (read-after-write). */
|
|
480
|
-
async artifactGet(id: string): Promise<JsonValue | undefined> {
|
|
481
|
-
const get = this.definition.get;
|
|
482
|
-
if (get === undefined) {
|
|
483
|
-
throw new Error("storage get is not configured");
|
|
484
|
-
}
|
|
485
|
-
const value = await get(
|
|
486
|
-
storageKeys.artifact(this.agentName, id),
|
|
487
|
-
this.context("restore")
|
|
488
|
-
);
|
|
489
|
-
return value ?? undefined;
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
/** Awaited artifact-row write (straight to the sink, like author KV). */
|
|
493
|
-
async artifactPut(id: string, value: JsonValue): Promise<void> {
|
|
494
|
-
await this.definition.put(
|
|
495
|
-
storageKeys.artifact(this.agentName, id),
|
|
496
|
-
value,
|
|
497
|
-
this.context("policy")
|
|
498
|
-
);
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
/** Awaited artifact-row delete. Errors propagate (cap eviction). */
|
|
502
|
-
async artifactDelete(id: string): Promise<void> {
|
|
503
|
-
const del = this.definition.delete;
|
|
504
|
-
if (del === undefined) {
|
|
505
|
-
throw new Error("storage delete is not configured");
|
|
506
|
-
}
|
|
507
|
-
await del(storageKeys.artifact(this.agentName, id), this.context("policy"));
|
|
508
|
-
}
|
|
509
|
-
|
|
510
|
-
/** All artifact-row values for this agent. Errors propagate. */
|
|
511
|
-
async artifactList(): Promise<JsonValue[]> {
|
|
512
|
-
const list = this.definition.list;
|
|
513
|
-
if (list === undefined) {
|
|
514
|
-
throw new Error("storage list is not configured");
|
|
515
|
-
}
|
|
516
|
-
const entries = await list(
|
|
517
|
-
storageKeys.artifactPrefix(this.agentName),
|
|
518
|
-
this.context("restore")
|
|
519
|
-
);
|
|
520
|
-
return Array.isArray(entries) ? entries.map((entry) => entry.value) : [];
|
|
521
|
-
}
|
|
522
|
-
|
|
523
461
|
// ==========================================================================
|
|
524
462
|
// Evals (dedicated `evals` table)
|
|
525
463
|
// ==========================================================================
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
writeFile,
|
|
20
20
|
} from "node:fs/promises";
|
|
21
21
|
import { dirname, join, parse, relative, resolve, sep } from "node:path";
|
|
22
|
+
import { agentStoreKeys, DEPLOYMENT_STORE_MOUNT_PATH } from "../files.js";
|
|
22
23
|
import { MEMORY_DIR_NAME } from "../memory.js";
|
|
23
24
|
import type {
|
|
24
25
|
DiscoveredSkill,
|
|
@@ -38,15 +39,28 @@ export interface MaterializeWorkspaceOptions {
|
|
|
38
39
|
seed: boolean;
|
|
39
40
|
/**
|
|
40
41
|
* Agent state root. `<stateRoot>/memory/` (the durable cross-session
|
|
41
|
-
* directory the memory hook journals into
|
|
42
|
-
* the
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
42
|
+
* directory the memory hook journals into — on hosting a local mirror of
|
|
43
|
+
* the Agent Store journal) is created and symlinked into the workspace as
|
|
44
|
+
* `memory` so the agent reads it with plain file tools. Cloud VMs have no
|
|
45
|
+
* materialized workspace; they read the store mount directly (see
|
|
46
|
+
* {@link buildCloudMemorySection}).
|
|
46
47
|
*/
|
|
47
48
|
stateRoot: string;
|
|
48
49
|
}
|
|
49
50
|
|
|
51
|
+
/**
|
|
52
|
+
* First-turn preamble section for cloud-runtime sessions on hosted
|
|
53
|
+
* deployments: cloud VMs read the memory journal from the store mount.
|
|
54
|
+
*/
|
|
55
|
+
export function buildCloudMemorySection(agentName: string): string {
|
|
56
|
+
const dir = `${DEPLOYMENT_STORE_MOUNT_PATH}/${agentStoreKeys.memoryDir(agentName)}`;
|
|
57
|
+
return [
|
|
58
|
+
"## Shared session memory",
|
|
59
|
+
"",
|
|
60
|
+
`The cross-session memory journal (the \`memory/\` directory your instructions may reference) lives at \`${dir}\` on this machine — read \`${dir}/journal.jsonl\` with your normal file tools.`,
|
|
61
|
+
].join("\n");
|
|
62
|
+
}
|
|
63
|
+
|
|
50
64
|
/** Relative path of an agent-side tool script inside the session workspace. */
|
|
51
65
|
export function agentToolScriptPath(toolName: string): string {
|
|
52
66
|
return `.agent-serve/tools/${toolName}.sh`;
|