@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.
Files changed (126) hide show
  1. package/README.md +2 -2
  2. package/dist/docs/404.html +2 -2
  3. package/dist/docs/ab.html +3 -3
  4. package/dist/docs/assets/{app.BEHVaPKt.js → app.kqdC1N7p.js} +1 -1
  5. package/dist/docs/assets/chunks/@localSearchIndexroot.BqhvUA76.js +1 -0
  6. package/dist/docs/assets/chunks/{VPLocalSearchBox.D2-5PKih.js → VPLocalSearchBox.DXDE2WgG.js} +1 -1
  7. package/dist/docs/assets/chunks/{theme.uNqJ4MUO.js → theme.DFYqbyta.js} +2 -2
  8. package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.js → guides_cloud-runtime.md.CbklWfzh.js} +1 -1
  9. package/dist/docs/assets/{guides_cloud-runtime.md.V5igN4Sq.lean.js → guides_cloud-runtime.md.CbklWfzh.lean.js} +1 -1
  10. package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.js → reference_agent-config.md.CTWp4DnU.js} +1 -1
  11. package/dist/docs/assets/{reference_skills.md.B_jHN7JL.js → reference_skills.md.BRF2nDv9.js} +2 -2
  12. package/dist/docs/assets/{reference_skills.md.B_jHN7JL.lean.js → reference_skills.md.BRF2nDv9.lean.js} +1 -1
  13. package/dist/docs/assets/troubleshooting.md.KgmmaCgw.js +1 -0
  14. package/dist/docs/building-with-agents.html +3 -3
  15. package/dist/docs/concepts.html +3 -3
  16. package/dist/docs/deployment.html +3 -3
  17. package/dist/docs/evals.html +3 -3
  18. package/dist/docs/example-agents/approval-buddy.html +3 -3
  19. package/dist/docs/example-agents/benny.html +3 -3
  20. package/dist/docs/example-agents/bugbot.html +3 -3
  21. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  22. package/dist/docs/example-agents/codeowners-review.html +3 -3
  23. package/dist/docs/example-agents/concierge.html +3 -3
  24. package/dist/docs/example-agents/fsd.html +3 -3
  25. package/dist/docs/example-agents/index.html +3 -3
  26. package/dist/docs/example-agents/knowledge-base.html +3 -3
  27. package/dist/docs/example-agents/oncall.html +3 -3
  28. package/dist/docs/example-agents/security-reviewer.html +3 -3
  29. package/dist/docs/example-agents/slack-agent.html +3 -3
  30. package/dist/docs/example-agents/weather-agent.html +3 -3
  31. package/dist/docs/guides/agent-to-agent.html +3 -3
  32. package/dist/docs/guides/cloud-runtime.html +5 -5
  33. package/dist/docs/guides/convert-automation.html +3 -3
  34. package/dist/docs/guides/github.html +3 -3
  35. package/dist/docs/guides/human-in-the-loop.html +3 -3
  36. package/dist/docs/guides/mcp-oauth.html +3 -3
  37. package/dist/docs/guides/opentelemetry.html +3 -3
  38. package/dist/docs/guides/slack.html +3 -3
  39. package/dist/docs/guides/webhooks.html +3 -3
  40. package/dist/docs/hashmap.json +1 -1
  41. package/dist/docs/hillclimbing.html +3 -3
  42. package/dist/docs/index.html +3 -3
  43. package/dist/docs/quickstart.html +3 -3
  44. package/dist/docs/reference/agent-config.html +5 -5
  45. package/dist/docs/reference/artifacts.html +3 -3
  46. package/dist/docs/reference/channels.html +3 -3
  47. package/dist/docs/reference/cli.html +3 -3
  48. package/dist/docs/reference/connections.html +3 -3
  49. package/dist/docs/reference/hooks.html +3 -3
  50. package/dist/docs/reference/http-api.html +3 -3
  51. package/dist/docs/reference/instructions.html +3 -3
  52. package/dist/docs/reference/playground.html +3 -3
  53. package/dist/docs/reference/project-layout.html +3 -3
  54. package/dist/docs/reference/prompt.html +3 -3
  55. package/dist/docs/reference/schedules.html +3 -3
  56. package/dist/docs/reference/sessions.html +3 -3
  57. package/dist/docs/reference/skills.html +5 -5
  58. package/dist/docs/reference/subagents.html +3 -3
  59. package/dist/docs/reference/tools.html +3 -3
  60. package/dist/docs/scaffolding-agents.html +3 -3
  61. package/dist/docs/storage.html +3 -3
  62. package/dist/docs/templates/agentic-owners.html +3 -3
  63. package/dist/docs/templates/demo.html +3 -3
  64. package/dist/docs/templates/pr-autofixer.html +3 -3
  65. package/dist/docs/templates/security-reviewer.html +3 -3
  66. package/dist/docs/templates/triage.html +3 -3
  67. package/dist/docs/troubleshooting.html +5 -5
  68. package/dist/files-backends/cursor-hosted.d.ts +6 -0
  69. package/dist/files-backends/cursor-hosted.d.ts.map +1 -1
  70. package/dist/files-backends/cursor-hosted.js +19 -0
  71. package/dist/files.d.ts +14 -1
  72. package/dist/files.d.ts.map +1 -1
  73. package/dist/files.js +14 -1
  74. package/dist/internal/discovery.d.ts.map +1 -1
  75. package/dist/internal/discovery.js +17 -2
  76. package/dist/internal/event-mapper.d.ts +7 -1
  77. package/dist/internal/event-mapper.d.ts.map +1 -1
  78. package/dist/internal/event-mapper.js +8 -8
  79. package/dist/internal/http-channel.d.ts.map +1 -1
  80. package/dist/internal/http-channel.js +1 -1
  81. package/dist/internal/mcp-endpoint.js +9 -0
  82. package/dist/internal/prompt-context.d.ts +4 -1
  83. package/dist/internal/prompt-context.d.ts.map +1 -1
  84. package/dist/internal/prompt-context.js +20 -2
  85. package/dist/internal/review-comments.js +15 -7
  86. package/dist/internal/sdk-runner.d.ts.map +1 -1
  87. package/dist/internal/sdk-runner.js +6 -9
  88. package/dist/internal/session-engine.d.ts +36 -2
  89. package/dist/internal/session-engine.d.ts.map +1 -1
  90. package/dist/internal/session-engine.js +143 -52
  91. package/dist/internal/skills-sync.d.ts +7 -5
  92. package/dist/internal/skills-sync.d.ts.map +1 -1
  93. package/dist/internal/skills-sync.js +9 -7
  94. package/dist/internal/tool-policy.d.ts +50 -0
  95. package/dist/internal/tool-policy.d.ts.map +1 -0
  96. package/dist/internal/tool-policy.js +51 -0
  97. package/dist/playground/assets/{index-CkuIrhg4.js → index-Cspgk9jC.js} +45 -45
  98. package/dist/playground/index.html +1 -1
  99. package/dist/tools.d.ts +16 -2
  100. package/dist/tools.d.ts.map +1 -1
  101. package/dist/types.d.ts +72 -0
  102. package/dist/types.d.ts.map +1 -1
  103. package/docs/guides/cloud-runtime.md +6 -1
  104. package/docs/reference/agent-config.md +6 -5
  105. package/docs/reference/skills.md +12 -3
  106. package/docs/troubleshooting.md +1 -1
  107. package/package.json +1 -1
  108. package/skills/framework-map/SKILL.md +2 -1
  109. package/src/files-backends/cursor-hosted.ts +21 -0
  110. package/src/files.ts +21 -2
  111. package/src/internal/discovery.ts +21 -1
  112. package/src/internal/event-mapper.ts +10 -1
  113. package/src/internal/http-channel.ts +1 -0
  114. package/src/internal/mcp-endpoint.ts +9 -0
  115. package/src/internal/prompt-context.ts +25 -2
  116. package/src/internal/review-comments.ts +17 -7
  117. package/src/internal/sdk-runner.ts +10 -11
  118. package/src/internal/session-engine.ts +174 -39
  119. package/src/internal/skills-sync.ts +10 -7
  120. package/src/internal/tool-policy.ts +92 -0
  121. package/src/tools.ts +19 -0
  122. package/src/types.ts +78 -0
  123. package/dist/docs/assets/chunks/@localSearchIndexroot.Vvio4stV.js +0 -1
  124. package/dist/docs/assets/troubleshooting.md.DQ-hYuZT.js +0 -1
  125. /package/dist/docs/assets/{reference_agent-config.md.BZnEKfEc.lean.js → reference_agent-config.md.CTWp4DnU.lean.js} +0 -0
  126. /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 { cursorHostedFiles, isCursorHostedFilesAvailable, } from "../files-backends/cursor-hosted.js";
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
- const mapper = new TurnEventMapper(emit);
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 deployment store at `skills/<name>/…`
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: cursorHostedFiles(),
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] deployment store sync failed: ${error instanceof Error ? error.message : String(error)}`);
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 hosted, not just when skills exist: removing the
1395
- // last skill has to delete its store copies too. Discovery is native
1396
- // (`--agent-store-skills-dir`); this only has to land the bytes.
1397
- if (hosted) {
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 })), { customTools: runtime === "local"
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 = (_a = args.toolCallId) !== null && _a !== void 0 ? _a : newToolCallId(args.tool.name);
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
- result = normalizeToolResult(yield execute(validation.value, {
1975
- toolCallId: callId,
1976
- session,
1977
- workspaceDir,
1978
- stateRoot: this.stateRoot,
1979
- host: this.hostForSession(record === null || record === void 0 ? void 0 : record.sessionId),
1980
- // Scratch calls have no real session; their facade stays unbound.
1981
- artifacts: record === undefined
1982
- ? this.artifactsApi()
1983
- : this.artifactsApi({
1984
- sessionId: record.sessionId,
1985
- turnId: callId,
1986
- }),
1987
- send: (channelId, message, sendOptions = {}) => this.send(channelId, message, Object.assign(Object.assign({}, sendOptions), { auth: sendOptions.auth !== undefined
1988
- ? sendOptions.auth
1989
- : session.auth })),
1990
- getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
1991
- emit,
1992
- }));
1993
- isError = isErrorEnvelope(result);
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, toolName, output: result, isError },
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 into the deployment's Agent Store so cloud-runtime
3
- * turns discover them natively. The engine writes through the hosted sink
4
- * at store-root `skills/<name>/…`; the cloud VM is launched with
5
- * `--agent-store-skills-dir /cursor/stores/deployment/skills` and walks that
6
- * directory the same way it walks a user store.
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;CACpC,GAAG,OAAO,CAAC,IAAI,CAAC,CAsBhB"}
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 into the deployment's Agent Store so cloud-runtime
3
- * turns discover them natively. The engine writes through the hosted sink
4
- * at store-root `skills/<name>/…`; the cloud VM is launched with
5
- * `--agent-store-skills-dir /cursor/stores/deployment/skills` and walks that
6
- * directory the same way it walks a user store.
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(agentStoreKeys.skillsPrefix()),
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
+ }