@cursor/july 0.1.82 → 0.1.84
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 +2 -2
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.BEHVaPKt.js → app.kqdC1N7p.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BqhvUA76.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.D2-5PKih.js → VPLocalSearchBox.DXDE2WgG.js} +1 -1
- package/dist/docs/assets/chunks/{theme.uNqJ4MUO.js → theme.DFYqbyta.js} +2 -2
- package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.js → guides_cloud-runtime.md.CbklWfzh.js} +1 -1
- package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.lean.js → guides_cloud-runtime.md.CbklWfzh.lean.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.js → reference_agent-config.md.CTWp4DnU.js} +1 -1
- package/dist/docs/assets/{reference_skills.md.B_jHN7JL.js → reference_skills.md.BRF2nDv9.js} +2 -2
- package/dist/docs/assets/{reference_skills.md.B_jHN7JL.lean.js → reference_skills.md.BRF2nDv9.lean.js} +1 -1
- package/dist/docs/assets/troubleshooting.md.KgmmaCgw.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 +3 -3
- package/dist/docs/guides/agent-to-agent.html +3 -3
- package/dist/docs/guides/cloud-runtime.html +5 -5
- package/dist/docs/guides/convert-automation.html +3 -3
- 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/opentelemetry.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 +5 -5
- package/dist/docs/reference/artifacts.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 +5 -5
- 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/templates/agentic-owners.html +3 -3
- package/dist/docs/templates/demo.html +3 -3
- package/dist/docs/templates/pr-autofixer.html +3 -3
- package/dist/docs/templates/security-reviewer.html +3 -3
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +5 -5
- package/dist/files-backends/cursor-hosted.d.ts +6 -0
- package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/files-backends/cursor-hosted.js +19 -0
- package/dist/files.d.ts +14 -1
- package/dist/files.d.ts.map +1 -1
- package/dist/files.js +14 -1
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +17 -2
- package/dist/internal/event-mapper.d.ts +7 -1
- package/dist/internal/event-mapper.d.ts.map +1 -1
- package/dist/internal/event-mapper.js +8 -8
- package/dist/internal/http-channel.d.ts.map +1 -1
- package/dist/internal/http-channel.js +1 -1
- package/dist/internal/mcp-endpoint.js +9 -0
- package/dist/internal/prompt-context.d.ts +4 -1
- package/dist/internal/prompt-context.d.ts.map +1 -1
- package/dist/internal/prompt-context.js +20 -2
- package/dist/internal/review-comments.js +15 -7
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +6 -9
- package/dist/internal/session-engine.d.ts +36 -2
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +143 -52
- package/dist/internal/skills-sync.d.ts +7 -5
- package/dist/internal/skills-sync.d.ts.map +1 -1
- package/dist/internal/skills-sync.js +9 -7
- package/dist/internal/tool-policy.d.ts +50 -0
- package/dist/internal/tool-policy.d.ts.map +1 -0
- package/dist/internal/tool-policy.js +51 -0
- package/dist/playground/assets/{index-CkuIrhg4.js → index-Cspgk9jC.js} +45 -45
- package/dist/playground/index.html +1 -1
- package/dist/tools.d.ts +16 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/types.d.ts +72 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/guides/cloud-runtime.md +6 -1
- package/docs/reference/agent-config.md +6 -5
- package/docs/reference/skills.md +12 -3
- package/docs/troubleshooting.md +1 -1
- package/package.json +1 -1
- package/skills/framework-map/SKILL.md +2 -1
- package/src/files-backends/cursor-hosted.ts +21 -0
- package/src/files.ts +21 -2
- package/src/internal/discovery.ts +21 -1
- package/src/internal/event-mapper.ts +10 -1
- package/src/internal/http-channel.ts +1 -0
- package/src/internal/mcp-endpoint.ts +9 -0
- package/src/internal/prompt-context.ts +25 -2
- package/src/internal/review-comments.ts +17 -7
- package/src/internal/sdk-runner.ts +10 -11
- package/src/internal/session-engine.ts +174 -39
- package/src/internal/skills-sync.ts +10 -7
- package/src/internal/tool-policy.ts +92 -0
- package/src/tools.ts +19 -0
- package/src/types.ts +78 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.Vvio4stV.js +0 -1
- package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +0 -1
- /package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.lean.js → reference_agent-config.md.CTWp4DnU.lean.js} +0 -0
- /package/dist/docs/assets/{troubleshooting.md.DQ-hYuZT.lean.js → troubleshooting.md.KgmmaCgw.lean.js} +0 -0
|
@@ -22,7 +22,8 @@ import { SLACK_CHANNEL_ID } from "../channels/slack/constants.js";
|
|
|
22
22
|
import { materializeSlackNudgeCarrier } from "../channels/slack/nudge-carrier.js";
|
|
23
23
|
import { SlackAskDedupeStore, SlackNudgeStore, } from "../channels/slack/nudge-store.js";
|
|
24
24
|
import { resolveDedicatedSlackHostBinding } from "../channels/slack/slack-channel.js";
|
|
25
|
-
import {
|
|
25
|
+
import { AGENT_SERVE_USER_STORE_FILES_SOURCE_ID, agentStoreKeys, } from "../files.js";
|
|
26
|
+
import { cursorHostedFiles, isCursorHostedFilesAvailable, isCursorUserStoreFilesAvailable, } from "../files-backends/cursor-hosted.js";
|
|
26
27
|
import { DEFAULT_MODEL, DIRECT_TOOL_CALL_CHANNEL_ID, isHostOauthConnectionTransport, isSymbolicConnectionTransport, } from "../types.js";
|
|
27
28
|
import { ABCollector } from "./ab-collector.js";
|
|
28
29
|
import { foldABStream } from "./ab-fold.js";
|
|
@@ -53,6 +54,7 @@ import { sessionExternalLinks } from "./session-links.js";
|
|
|
53
54
|
import { SessionStore } from "./session-store.js";
|
|
54
55
|
import { syncSkillsToStore } from "./skills-sync.js";
|
|
55
56
|
import { StorageCoordinator } from "./storage-coordinator.js";
|
|
57
|
+
import { decideToolCall } from "./tool-policy.js";
|
|
56
58
|
import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
|
|
57
59
|
import { estimateTurnCostFromApi, roundCostUsd, } from "./turn-cost.js";
|
|
58
60
|
import { TurnGovernor } from "./turn-governor.js";
|
|
@@ -123,6 +125,15 @@ export class SessionEngine {
|
|
|
123
125
|
this.activeTurns = new Map();
|
|
124
126
|
/** Context used by the cloud turn's HTTP MCP callbacks. */
|
|
125
127
|
this.activeToolContexts = new Map();
|
|
128
|
+
/**
|
|
129
|
+
* Tool call ids the host answered instead of running, per session.
|
|
130
|
+
*
|
|
131
|
+
* The mapper emits the result event from the harness's view of a finished
|
|
132
|
+
* call, which cannot tell an answered call from a real one — so the decision
|
|
133
|
+
* point records the id here and the mapper stamps the event from it.
|
|
134
|
+
* Registered only while a turn can invoke a tool.
|
|
135
|
+
*/
|
|
136
|
+
this.stubbedCallIds = new Map();
|
|
126
137
|
/**
|
|
127
138
|
* Delivered-nudge count per session at the last tool boundary. The carrier
|
|
128
139
|
* drains out-of-process, so a growing count is how the engine learns a
|
|
@@ -529,24 +540,7 @@ export class SessionEngine {
|
|
|
529
540
|
const now = new Date().toISOString();
|
|
530
541
|
const channelState = this.initialChannelState(channelId, sendOptions.state);
|
|
531
542
|
const purpose = sendOptions.purpose === "eval" ? "eval" : "live";
|
|
532
|
-
let record = {
|
|
533
|
-
sessionId: newSessionId(),
|
|
534
|
-
channelId,
|
|
535
|
-
mode: (_d = sendOptions.mode) !== null && _d !== void 0 ? _d : "chat",
|
|
536
|
-
purpose,
|
|
537
|
-
continuationKey: (_e = sendOptions.continuationToken) !== null && _e !== void 0 ? _e : newContinuationKey(),
|
|
538
|
-
auth,
|
|
539
|
-
title: sendOptions.title,
|
|
540
|
-
channelState,
|
|
541
|
-
workspaceOverride: sendOptions.workspaceDir,
|
|
542
|
-
pendingWorkspaceFiles: sendOptions.workspaceFiles,
|
|
543
|
-
pendingImages: sendOptions.images,
|
|
544
|
-
cloudOverride: sendOptions.cloud,
|
|
545
|
-
sdkAgentId: sendOptions.sdkAgentId,
|
|
546
|
-
turnCount: 0,
|
|
547
|
-
createdAt: now,
|
|
548
|
-
updatedAt: now,
|
|
549
|
-
};
|
|
543
|
+
let record = Object.assign(Object.assign({ sessionId: newSessionId(), channelId, mode: (_d = sendOptions.mode) !== null && _d !== void 0 ? _d : "chat", purpose }, (sendOptions.dryRun === true ? { dryRun: true } : {})), { continuationKey: (_e = sendOptions.continuationToken) !== null && _e !== void 0 ? _e : newContinuationKey(), auth, title: sendOptions.title, channelState, workspaceOverride: sendOptions.workspaceDir, pendingWorkspaceFiles: sendOptions.workspaceFiles, pendingImages: sendOptions.images, cloudOverride: sendOptions.cloud, sdkAgentId: sendOptions.sdkAgentId, turnCount: 0, createdAt: now, updatedAt: now });
|
|
550
544
|
yield this.sessions.save(record);
|
|
551
545
|
(_a = this.storage) === null || _a === void 0 ? void 0 : _a.sessionRecord(record);
|
|
552
546
|
yield this.appendEvent(record.sessionId, {
|
|
@@ -1129,7 +1123,10 @@ export class SessionEngine {
|
|
|
1129
1123
|
this.logger(`[agent-sdk] failed to record event for ${sessionId}: ${describeError(error)}`);
|
|
1130
1124
|
}
|
|
1131
1125
|
};
|
|
1132
|
-
|
|
1126
|
+
// Read by closure, so the mapper still sees it while draining after the
|
|
1127
|
+
// registry entry below is gone.
|
|
1128
|
+
const stubbed = new Set();
|
|
1129
|
+
const mapper = new TurnEventMapper(emit, (callId) => stubbed.has(callId));
|
|
1133
1130
|
// Take a turn slot before any work happens. When every slot is busy the
|
|
1134
1131
|
// turn waits FIFO — durable `turn.queued` marks the wait — and a
|
|
1135
1132
|
// preempting follow-up (or interrupt) resolves the wait as cancelled
|
|
@@ -1213,11 +1210,13 @@ export class SessionEngine {
|
|
|
1213
1210
|
}
|
|
1214
1211
|
}), signal, pendingImages);
|
|
1215
1212
|
this.activeToolContexts.set(sessionId, { record, turnId, emit });
|
|
1213
|
+
this.stubbedCallIds.set(sessionId, stubbed);
|
|
1216
1214
|
try {
|
|
1217
1215
|
outcome = yield this.runner.runTurn(request);
|
|
1218
1216
|
}
|
|
1219
1217
|
finally {
|
|
1220
1218
|
this.activeToolContexts.delete(sessionId);
|
|
1219
|
+
this.stubbedCallIds.delete(sessionId);
|
|
1221
1220
|
}
|
|
1222
1221
|
}
|
|
1223
1222
|
catch (error) {
|
|
@@ -1294,7 +1293,8 @@ export class SessionEngine {
|
|
|
1294
1293
|
});
|
|
1295
1294
|
}
|
|
1296
1295
|
/**
|
|
1297
|
-
* Sync authored skills onto the
|
|
1296
|
+
* Sync authored skills onto the Agent Store at `skills/<name>/…` (hosted)
|
|
1297
|
+
* or `agent-serve/<agent>/skills/<name>/…` (personal-key local+cloud)
|
|
1298
1298
|
* before the first cloud prompt, so `--agent-store-skills-dir` and the
|
|
1299
1299
|
* first-turn S3 scan can discover them. False when the sync fails: the
|
|
1300
1300
|
* turn then runs without store skills rather than pointing discovery at
|
|
@@ -1315,13 +1315,19 @@ export class SessionEngine {
|
|
|
1315
1315
|
if (this.cloudSkillsSync !== undefined) {
|
|
1316
1316
|
return this.cloudSkillsSync;
|
|
1317
1317
|
}
|
|
1318
|
+
const target = this.cloudSkillsStore();
|
|
1319
|
+
if (target === undefined) {
|
|
1320
|
+
this.cloudSkillsSync = Promise.resolve(false);
|
|
1321
|
+
return this.cloudSkillsSync;
|
|
1322
|
+
}
|
|
1318
1323
|
const skills = this.project.agent.skills;
|
|
1319
1324
|
let sync = syncSkillsToStore({
|
|
1320
|
-
sink:
|
|
1325
|
+
sink: target.sink,
|
|
1321
1326
|
agentName: this.project.name,
|
|
1322
1327
|
skills,
|
|
1328
|
+
skillsPrefix: target.skillsPrefix,
|
|
1323
1329
|
}).then(() => true, (error) => {
|
|
1324
|
-
this.logger(`[skills]
|
|
1330
|
+
this.logger(`[skills] ${target.label} sync failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
1325
1331
|
return false;
|
|
1326
1332
|
});
|
|
1327
1333
|
if (skills.length > 0) {
|
|
@@ -1332,6 +1338,29 @@ export class SessionEngine {
|
|
|
1332
1338
|
this.cloudSkillsSync = sync;
|
|
1333
1339
|
return sync;
|
|
1334
1340
|
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Hosted deployments own the deployment store. Local `serve`/`run` with
|
|
1343
|
+
* a personal API key writes into the USER store under `agent-serve/`.
|
|
1344
|
+
*/
|
|
1345
|
+
cloudSkillsStore() {
|
|
1346
|
+
if (isCursorHostedFilesAvailable()) {
|
|
1347
|
+
return {
|
|
1348
|
+
sink: cursorHostedFiles(),
|
|
1349
|
+
skillsPrefix: agentStoreKeys.skillsPrefix(),
|
|
1350
|
+
label: "deployment store",
|
|
1351
|
+
};
|
|
1352
|
+
}
|
|
1353
|
+
if (isCursorUserStoreFilesAvailable()) {
|
|
1354
|
+
return {
|
|
1355
|
+
sink: cursorHostedFiles({
|
|
1356
|
+
storeSourceId: AGENT_SERVE_USER_STORE_FILES_SOURCE_ID,
|
|
1357
|
+
}),
|
|
1358
|
+
skillsPrefix: agentStoreKeys.userSkillsPrefix(this.project.name),
|
|
1359
|
+
label: "user store",
|
|
1360
|
+
};
|
|
1361
|
+
}
|
|
1362
|
+
return undefined;
|
|
1363
|
+
}
|
|
1335
1364
|
buildTurnRequest(record, agent, turnId, prompt, isFirstTurn, instructionsInvisibleToHarness, emit, onUpdate, signal, images) {
|
|
1336
1365
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1337
1366
|
var _a, _b, _c;
|
|
@@ -1391,10 +1420,11 @@ export class SessionEngine {
|
|
|
1391
1420
|
: SERVER_TOOLS_MCP_SERVER_NAME,
|
|
1392
1421
|
});
|
|
1393
1422
|
const hosted = isCursorHostedFilesAvailable();
|
|
1394
|
-
// Synced whenever
|
|
1395
|
-
// last skill has to delete its store copies too.
|
|
1396
|
-
// (`--agent-store-skills-dir`); this only has
|
|
1397
|
-
|
|
1423
|
+
// Synced whenever a store is reachable, not just when skills exist:
|
|
1424
|
+
// removing the last skill has to delete its store copies too.
|
|
1425
|
+
// Discovery is native (`--agent-store-skills-dir`); this only has
|
|
1426
|
+
// to land the bytes.
|
|
1427
|
+
if (hosted || isCursorUserStoreFilesAvailable()) {
|
|
1398
1428
|
yield this.ensureCloudSkillsSynced();
|
|
1399
1429
|
}
|
|
1400
1430
|
const memorySection = hosted
|
|
@@ -1412,11 +1442,13 @@ export class SessionEngine {
|
|
|
1412
1442
|
turnPrompt = `${preamble}\n\n${turnPrompt}`;
|
|
1413
1443
|
}
|
|
1414
1444
|
}
|
|
1415
|
-
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
|
|
1445
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ sessionId: record.sessionId }, (record.sdkAgentId === undefined
|
|
1416
1446
|
? {}
|
|
1417
1447
|
: { sdkAgentId: record.sdkAgentId })), { workspaceDir: this.harnessCwd(record), sandbox: ((_c = this.project.agent.local) === null || _c === void 0 ? void 0 : _c.sandbox) === true }), (runtime === "local" && agent.allowedTools !== undefined
|
|
1418
1448
|
? { tools: agent.allowedTools }
|
|
1419
|
-
: {})), { runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (images !== undefined && images.length > 0 ? { images } : {})), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })),
|
|
1449
|
+
: {})), { runnerStateDir: join(this.stateRoot, "runner"), prompt: turnPrompt, isFirstTurn }), (images !== undefined && images.length > 0 ? { images } : {})), (agent.model === undefined ? {} : { model: agent.model })), { runtime }), (mergedCloud === undefined ? {} : { cloud: mergedCloud })), (runtime === "cloud" && !isCursorHostedFilesAvailable()
|
|
1450
|
+
? { agentServeAgent: this.project.name }
|
|
1451
|
+
: {})), { customTools: runtime === "local"
|
|
1420
1452
|
? yield this.buildCustomTools(record, agent, turnId, emit)
|
|
1421
1453
|
: {}, mcpServers: this.buildMcpServers(runtime, record.sessionId, yield this.evaluateConnectionAuth(record)) }), (runtime === "local" && this.hasAttachedAuthedConnections()
|
|
1422
1454
|
? { perSendMcpServers: true }
|
|
@@ -1718,6 +1750,7 @@ export class SessionEngine {
|
|
|
1718
1750
|
executeServerTool(args) {
|
|
1719
1751
|
return __awaiter(this, void 0, void 0, function* () {
|
|
1720
1752
|
var _a;
|
|
1753
|
+
var _b;
|
|
1721
1754
|
const execute = args.tool.definition.execute;
|
|
1722
1755
|
if (args.tool.execution !== "server" || typeof execute !== "function") {
|
|
1723
1756
|
return toolErrorResult(`Tool "${args.tool.name}" is not a server tool.`);
|
|
@@ -1726,7 +1759,7 @@ export class SessionEngine {
|
|
|
1726
1759
|
if (!validation.ok) {
|
|
1727
1760
|
return toolErrorResult(`Invalid input for tool "${args.tool.name}": ${validation.message}`);
|
|
1728
1761
|
}
|
|
1729
|
-
const toolCallId = (
|
|
1762
|
+
const toolCallId = (_b = args.toolCallId) !== null && _b !== void 0 ? _b : newToolCallId(args.tool.name);
|
|
1730
1763
|
const toolCtx = {
|
|
1731
1764
|
toolCallId,
|
|
1732
1765
|
session: this.sessionInfo(args.record),
|
|
@@ -1742,6 +1775,19 @@ export class SessionEngine {
|
|
|
1742
1775
|
emit: args.emit,
|
|
1743
1776
|
};
|
|
1744
1777
|
try {
|
|
1778
|
+
// Before the approval gate: a dry run is unattended, so parking a write
|
|
1779
|
+
// for a human would hang the turn rather than answer it.
|
|
1780
|
+
const decision = decideToolCall({
|
|
1781
|
+
dryRun: args.record.dryRun === true,
|
|
1782
|
+
effect: args.tool.definition.effect,
|
|
1783
|
+
dryRunResult: args.tool.definition.dryRunResult,
|
|
1784
|
+
input: validation.value,
|
|
1785
|
+
});
|
|
1786
|
+
if (decision.kind === "answer") {
|
|
1787
|
+
(_a = this.stubbedCallIds.get(args.record.sessionId)) === null || _a === void 0 ? void 0 : _a.add(toolCallId);
|
|
1788
|
+
this.logger(`[session] tool ${args.tool.name} answered by host (${toolCallId})`);
|
|
1789
|
+
return normalizeToolResult(decision.result);
|
|
1790
|
+
}
|
|
1745
1791
|
const needsGate = yield evaluateNeedsApproval(args.tool.definition.needsApproval, validation.value);
|
|
1746
1792
|
if (needsGate) {
|
|
1747
1793
|
const decision = yield this.parkForApproval({
|
|
@@ -1960,10 +2006,20 @@ export class SessionEngine {
|
|
|
1960
2006
|
data: { calls: [{ callId, toolName, args: validation.value }] },
|
|
1961
2007
|
});
|
|
1962
2008
|
}
|
|
2009
|
+
// A separate body from the model path, so it asks the same policy: a
|
|
2010
|
+
// session-bound `POST /v1/tools/:name`, `call --session`, or
|
|
2011
|
+
// `handle.callTool` performs a write just as readily.
|
|
2012
|
+
const decision = decideToolCall({
|
|
2013
|
+
dryRun: (record === null || record === void 0 ? void 0 : record.dryRun) === true,
|
|
2014
|
+
effect: tool.definition.effect,
|
|
2015
|
+
dryRunResult: tool.definition.dryRunResult,
|
|
2016
|
+
input: validation.value,
|
|
2017
|
+
});
|
|
1963
2018
|
const started = Date.now();
|
|
1964
2019
|
this.logger(`[session] tool ${toolName} start (direct ${callId})`);
|
|
1965
2020
|
let result;
|
|
1966
2021
|
let isError;
|
|
2022
|
+
const stubbed = decision.kind === "answer";
|
|
1967
2023
|
const live = record;
|
|
1968
2024
|
const emit = live === undefined
|
|
1969
2025
|
? undefined
|
|
@@ -1971,26 +2027,33 @@ export class SessionEngine {
|
|
|
1971
2027
|
void this.appendEvent(live.sessionId, Object.assign(Object.assign({}, payload), { turnId: callId })).catch(() => { });
|
|
1972
2028
|
};
|
|
1973
2029
|
try {
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
?
|
|
1989
|
-
:
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
2030
|
+
if (decision.kind === "answer") {
|
|
2031
|
+
this.logger(`[session] tool ${toolName} answered by host (direct ${callId})`);
|
|
2032
|
+
result = normalizeToolResult(decision.result);
|
|
2033
|
+
isError = false;
|
|
2034
|
+
}
|
|
2035
|
+
else {
|
|
2036
|
+
result = normalizeToolResult(yield execute(validation.value, {
|
|
2037
|
+
toolCallId: callId,
|
|
2038
|
+
session,
|
|
2039
|
+
workspaceDir,
|
|
2040
|
+
stateRoot: this.stateRoot,
|
|
2041
|
+
host: this.hostForSession(record === null || record === void 0 ? void 0 : record.sessionId),
|
|
2042
|
+
// Scratch calls have no real session; their facade stays unbound.
|
|
2043
|
+
artifacts: record === undefined
|
|
2044
|
+
? this.artifactsApi()
|
|
2045
|
+
: this.artifactsApi({
|
|
2046
|
+
sessionId: record.sessionId,
|
|
2047
|
+
turnId: callId,
|
|
2048
|
+
}),
|
|
2049
|
+
send: (channelId, message, sendOptions = {}) => this.send(channelId, message, Object.assign(Object.assign({}, sendOptions), { auth: sendOptions.auth !== undefined
|
|
2050
|
+
? sendOptions.auth
|
|
2051
|
+
: session.auth })),
|
|
2052
|
+
getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
|
|
2053
|
+
emit,
|
|
2054
|
+
}));
|
|
2055
|
+
isError = isErrorEnvelope(result);
|
|
2056
|
+
}
|
|
1994
2057
|
}
|
|
1995
2058
|
catch (error) {
|
|
1996
2059
|
this.logger(`[session] tool ${toolName} failed (direct ${callId}): ${describeError(error)}`);
|
|
@@ -2017,7 +2080,8 @@ export class SessionEngine {
|
|
|
2017
2080
|
yield this.appendEvent(record.sessionId, {
|
|
2018
2081
|
type: "action.result",
|
|
2019
2082
|
turnId: callId,
|
|
2020
|
-
data: { callId,
|
|
2083
|
+
data: Object.assign({ callId,
|
|
2084
|
+
toolName, output: result, isError }, (stubbed ? { stubbed: true } : {})),
|
|
2021
2085
|
});
|
|
2022
2086
|
}
|
|
2023
2087
|
const errorMessage = isError ? toolCallErrorMessage(result) : undefined;
|
|
@@ -2028,6 +2092,33 @@ export class SessionEngine {
|
|
|
2028
2092
|
durationMs }, (errorMessage === undefined ? {} : { errorMessage })), (record === undefined ? {} : { sessionId: record.sessionId }));
|
|
2029
2093
|
});
|
|
2030
2094
|
}
|
|
2095
|
+
/**
|
|
2096
|
+
* Whether a dry run answers a bridged MCP call, for the cloud host bridge —
|
|
2097
|
+
* the one tool path that does not reach {@link executeServerTool}, because
|
|
2098
|
+
* it forwards to `host.mcp` instead of invoking a tool body.
|
|
2099
|
+
*
|
|
2100
|
+
* A bridged tool carries no `defineTool`, so it declares no effect, and the
|
|
2101
|
+
* policy reads an undeclared tool as a write. In a dry run that answers
|
|
2102
|
+
* bridged reads too; resolving effects from a server's own listing is the
|
|
2103
|
+
* next unit's work.
|
|
2104
|
+
*/
|
|
2105
|
+
bridgedCallAnswer(args) {
|
|
2106
|
+
var _a;
|
|
2107
|
+
// Read off the in-flight turn's context rather than the session store:
|
|
2108
|
+
// the bridge already refuses a call with no active turn, so the record is
|
|
2109
|
+
// in memory. That keeps this free for the sessions that are not dry runs,
|
|
2110
|
+
// which is every session today.
|
|
2111
|
+
const record = (_a = this.activeToolContexts.get(args.sessionId)) === null || _a === void 0 ? void 0 : _a.record;
|
|
2112
|
+
const decision = decideToolCall({
|
|
2113
|
+
dryRun: (record === null || record === void 0 ? void 0 : record.dryRun) === true,
|
|
2114
|
+
effect: undefined,
|
|
2115
|
+
});
|
|
2116
|
+
if (decision.kind === "run") {
|
|
2117
|
+
return { answered: false };
|
|
2118
|
+
}
|
|
2119
|
+
this.logger(`[session] bridged tool ${args.toolName} answered by host (${args.sessionId})`);
|
|
2120
|
+
return { answered: true, result: normalizeToolResult(decision.result) };
|
|
2121
|
+
}
|
|
2031
2122
|
// ==========================================================================
|
|
2032
2123
|
// Events: append, dispatch to channel handlers + hooks
|
|
2033
2124
|
// ==========================================================================
|
|
@@ -2575,7 +2666,7 @@ export class SessionEngine {
|
|
|
2575
2666
|
this.coalesceDrains.has(sessionId));
|
|
2576
2667
|
}
|
|
2577
2668
|
sessionInfo(record) {
|
|
2578
|
-
return Object.assign(Object.assign(Object.assign({ id: record.sessionId, channelId: record.channelId, mode: record.mode, purpose: record.purpose === "eval" ? "eval" : "live", auth: record.auth, continuationKey: record.continuationKey }, (record.abs === undefined ? {} : { abs: record.abs })), (record.title === undefined ? {} : { title: record.title })), (record.sdkAgentId === undefined
|
|
2669
|
+
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ id: record.sessionId, channelId: record.channelId, mode: record.mode, purpose: record.purpose === "eval" ? "eval" : "live" }, (record.dryRun === true ? { dryRun: true } : {})), { auth: record.auth, continuationKey: record.continuationKey }), (record.abs === undefined ? {} : { abs: record.abs })), (record.title === undefined ? {} : { title: record.title })), (record.sdkAgentId === undefined
|
|
2579
2670
|
? {}
|
|
2580
2671
|
: { sdkAgentId: record.sdkAgentId }));
|
|
2581
2672
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sync authored skills
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Sync authored skills onto an Agent Store so cloud-runtime turns discover
|
|
3
|
+
* them natively. Hosted deployments write store-root `skills/<name>/…` (the
|
|
4
|
+
* VM's `--agent-store-skills-dir /cursor/stores/deployment/skills`). Local
|
|
5
|
+
* `serve`/`run` with `runtime: "cloud"` writes
|
|
6
|
+
* `agent-serve/<agent>/skills/<name>/…` on the USER store.
|
|
7
7
|
*/
|
|
8
8
|
import { type FileSink } from "../files.js";
|
|
9
9
|
import type { DiscoveredSkill } from "../types.js";
|
|
@@ -21,5 +21,7 @@ export declare function syncSkillsToStore(args: {
|
|
|
21
21
|
/** Sweeps leftover keys from the pre-native `agentkit/v1/{agent}/skills/` prefix. */
|
|
22
22
|
agentName: string;
|
|
23
23
|
skills: readonly DiscoveredSkill[];
|
|
24
|
+
/** Store-root directory that holds SKILL.md files. Defaults to `skills/`. */
|
|
25
|
+
skillsPrefix?: string;
|
|
24
26
|
}): Promise<void>;
|
|
25
27
|
//# sourceMappingURL=skills-sync.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skills-sync.d.ts","sourceRoot":"","sources":["../../src/internal/skills-sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,aAAa,CAAC;AAQpE;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,IAAI,EAAE,QAAQ,CAAC;IACf,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;
|
|
1
|
+
{"version":3,"file":"skills-sync.d.ts","sourceRoot":"","sources":["../../src/internal/skills-sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,EAAqC,KAAK,QAAQ,EAAE,MAAM,aAAa,CAAC;AAE/E,OAAO,KAAK,EAAE,eAAe,EAAmB,MAAM,aAAa,CAAC;AAQpE;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,IAAI,EAAE;IAC5C,IAAI,EAAE,QAAQ,CAAC;IACf,qFAAqF;IACrF,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,SAAS,eAAe,EAAE,CAAC;IACnC,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,GAAG,OAAO,CAAC,IAAI,CAAC,CAuBhB"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Sync authored skills
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
2
|
+
* Sync authored skills onto an Agent Store so cloud-runtime turns discover
|
|
3
|
+
* them natively. Hosted deployments write store-root `skills/<name>/…` (the
|
|
4
|
+
* VM's `--agent-store-skills-dir /cursor/stores/deployment/skills`). Local
|
|
5
|
+
* `serve`/`run` with `runtime: "cloud"` writes
|
|
6
|
+
* `agent-serve/<agent>/skills/<name>/…` on the USER store.
|
|
7
7
|
*/
|
|
8
8
|
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
9
|
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
@@ -34,9 +34,11 @@ function legacySkillsPrefix(agent) {
|
|
|
34
34
|
*/
|
|
35
35
|
export function syncSkillsToStore(args) {
|
|
36
36
|
return __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
var _a;
|
|
38
|
+
const skillsPrefix = (_a = args.skillsPrefix) !== null && _a !== void 0 ? _a : agentStoreKeys.skillsPrefix();
|
|
37
39
|
const desired = new Map();
|
|
38
40
|
for (const skill of args.skills) {
|
|
39
|
-
const key = (relPath) => agentStoreKeys.skillFile(skill.name, relPath);
|
|
41
|
+
const key = (relPath) => agentStoreKeys.skillFile(skill.name, relPath, skillsPrefix);
|
|
40
42
|
desired.set(key("SKILL.md"), Buffer.from(buildSkillMd(skill), "utf8"));
|
|
41
43
|
for (const [relPath, source] of Object.entries(skill.files)) {
|
|
42
44
|
desired.set(key(storeRelPath(relPath)), yield sourceBytes(source));
|
|
@@ -44,7 +46,7 @@ export function syncSkillsToStore(args) {
|
|
|
44
46
|
}
|
|
45
47
|
yield Promise.all([...desired].map(([key, body]) => putLastWriterWins(args.sink, key, body)));
|
|
46
48
|
const [existing, leftover] = yield Promise.all([
|
|
47
|
-
args.sink.list(
|
|
49
|
+
args.sink.list(skillsPrefix),
|
|
48
50
|
args.sink.list(legacySkillsPrefix(args.agentName)),
|
|
49
51
|
]);
|
|
50
52
|
yield Promise.all([...existing.filter((entry) => !desired.has(entry.key)), ...leftover].map((entry) => args.sink.delete(entry.key)));
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one decision every tool call passes through.
|
|
3
|
+
*
|
|
4
|
+
* A tool body can be reached by more than one path — the model's own calls on
|
|
5
|
+
* a local turn, a cloud turn's calls over the MCP endpoint, a direct
|
|
6
|
+
* `callTool`, the host connection bridge. Each of those is a separate code
|
|
7
|
+
* path, and a guarantee that holds on some of them is not a guarantee. So the
|
|
8
|
+
* decision lives here, in one pure function, and every path asks it rather
|
|
9
|
+
* than re-implementing it.
|
|
10
|
+
*/
|
|
11
|
+
import type { ToolEffect, ToolEffectDeclaration, ToolExecuteResult } from "../types.js";
|
|
12
|
+
/** What the host does with one tool call. */
|
|
13
|
+
export type ToolDecision =
|
|
14
|
+
/** Run the tool body. */
|
|
15
|
+
{
|
|
16
|
+
kind: "run";
|
|
17
|
+
}
|
|
18
|
+
/** Do not run it; return this to the model instead. */
|
|
19
|
+
| {
|
|
20
|
+
kind: "answer";
|
|
21
|
+
result: ToolExecuteResult;
|
|
22
|
+
};
|
|
23
|
+
/** What a policy decision needs to know about the call. */
|
|
24
|
+
export interface ToolCallFacts {
|
|
25
|
+
/** The session answers writes instead of running them. */
|
|
26
|
+
dryRun: boolean;
|
|
27
|
+
/** The tool's declaration, if it made one. */
|
|
28
|
+
effect: ToolEffectDeclaration<never> | undefined;
|
|
29
|
+
/** What a stubbed write should answer with, if the tool supplied one. */
|
|
30
|
+
dryRunResult?: ToolExecuteResult | ((input: never) => ToolExecuteResult);
|
|
31
|
+
/**
|
|
32
|
+
* Validated input, as the tool body would receive it. Only read by a
|
|
33
|
+
* per-call `effect` predicate and by a `dryRunResult` function, so a caller
|
|
34
|
+
* with neither — a bridged MCP tool, which carries no declaration at all —
|
|
35
|
+
* can omit it.
|
|
36
|
+
*/
|
|
37
|
+
input?: unknown;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* The effect of one specific call.
|
|
41
|
+
*
|
|
42
|
+
* Undeclared resolves to `"write"`. A dry run promises that nothing outside
|
|
43
|
+
* the session changed, and it cannot promise that about a tool it knows
|
|
44
|
+
* nothing about — so the unknown case is the one that costs a stubbed read
|
|
45
|
+
* rather than the one that leaks a real write.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveEffect(effect: ToolEffectDeclaration<never> | undefined, input: unknown): ToolEffect;
|
|
48
|
+
/** Decide what to do with one tool call. Pure; no I/O, no session state. */
|
|
49
|
+
export declare function decideToolCall(facts: ToolCallFacts): ToolDecision;
|
|
50
|
+
//# sourceMappingURL=tool-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tool-policy.d.ts","sourceRoot":"","sources":["../../src/internal/tool-policy.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EACV,UAAU,EACV,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,aAAa,CAAC;AAErB,6CAA6C;AAC7C,MAAM,MAAM,YAAY;AACtB,yBAAyB;AACvB;IAAE,IAAI,EAAE,KAAK,CAAA;CAAE;AACjB,uDAAuD;GACrD;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,iBAAiB,CAAA;CAAE,CAAC;AAElD,2DAA2D;AAC3D,MAAM,WAAW,aAAa;IAC5B,0DAA0D;IAC1D,MAAM,EAAE,OAAO,CAAC;IAChB,8CAA8C;IAC9C,MAAM,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,SAAS,CAAC;IACjD,yEAAyE;IACzE,YAAY,CAAC,EAAE,iBAAiB,GAAG,CAAC,CAAC,KAAK,EAAE,KAAK,KAAK,iBAAiB,CAAC,CAAC;IACzE;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAUD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,MAAM,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,SAAS,EAChD,KAAK,EAAE,OAAO,GACb,UAAU,CAIZ;AAED,4EAA4E;AAC5E,wBAAgB,cAAc,CAAC,KAAK,EAAE,aAAa,GAAG,YAAY,CAWjE"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The one decision every tool call passes through.
|
|
3
|
+
*
|
|
4
|
+
* A tool body can be reached by more than one path — the model's own calls on
|
|
5
|
+
* a local turn, a cloud turn's calls over the MCP endpoint, a direct
|
|
6
|
+
* `callTool`, the host connection bridge. Each of those is a separate code
|
|
7
|
+
* path, and a guarantee that holds on some of them is not a guarantee. So the
|
|
8
|
+
* decision lives here, in one pure function, and every path asks it rather
|
|
9
|
+
* than re-implementing it.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* What a stubbed write answers. Deliberately not self-describing: a model told
|
|
13
|
+
* mid-turn that its writes are not real starts hedging its conclusions, and
|
|
14
|
+
* the conclusion is the thing a dry run exists to read. Tell the agent once,
|
|
15
|
+
* in its opening prompt, not in a tool result.
|
|
16
|
+
*/
|
|
17
|
+
const ACKNOWLEDGED = "Operation acknowledged.";
|
|
18
|
+
/**
|
|
19
|
+
* The effect of one specific call.
|
|
20
|
+
*
|
|
21
|
+
* Undeclared resolves to `"write"`. A dry run promises that nothing outside
|
|
22
|
+
* the session changed, and it cannot promise that about a tool it knows
|
|
23
|
+
* nothing about — so the unknown case is the one that costs a stubbed read
|
|
24
|
+
* rather than the one that leaks a real write.
|
|
25
|
+
*/
|
|
26
|
+
export function resolveEffect(effect, input) {
|
|
27
|
+
const declared = typeof effect === "function" ? effect(input) : effect;
|
|
28
|
+
return declared === "read" ? "read" : "write";
|
|
29
|
+
}
|
|
30
|
+
/** Decide what to do with one tool call. Pure; no I/O, no session state. */
|
|
31
|
+
export function decideToolCall(facts) {
|
|
32
|
+
if (!facts.dryRun) {
|
|
33
|
+
return { kind: "run" };
|
|
34
|
+
}
|
|
35
|
+
if (resolveEffect(facts.effect, facts.input) === "read") {
|
|
36
|
+
return { kind: "run" };
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
kind: "answer",
|
|
40
|
+
result: dryRunAnswer(facts.dryRunResult, facts.input),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function dryRunAnswer(dryRunResult, input) {
|
|
44
|
+
if (dryRunResult === undefined) {
|
|
45
|
+
return ACKNOWLEDGED;
|
|
46
|
+
}
|
|
47
|
+
if (typeof dryRunResult === "function") {
|
|
48
|
+
return dryRunResult(input);
|
|
49
|
+
}
|
|
50
|
+
return dryRunResult;
|
|
51
|
+
}
|