@cursor/july 0.1.83 → 0.1.85

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 (118) hide show
  1. package/dist/channels/deployments/deployments-channel.d.ts +2 -2
  2. package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
  3. package/dist/channels/deployments/deployments-channel.js +7 -6
  4. package/dist/channels/deployments/index.d.ts +2 -3
  5. package/dist/channels/deployments/index.d.ts.map +1 -1
  6. package/dist/channels/deployments/index.js +2 -3
  7. package/dist/channels/deployments/types.d.ts +14 -14
  8. package/dist/channels/deployments/types.d.ts.map +1 -1
  9. package/dist/docs/404.html +2 -2
  10. package/dist/docs/ab.html +3 -3
  11. package/dist/docs/assets/{app.6THHGRH-.js → app.DUOPbN18.js} +1 -1
  12. package/dist/docs/assets/chunks/@localSearchIndexroot.CxCtxfDE.js +1 -0
  13. package/dist/docs/assets/chunks/{VPLocalSearchBox.BpHB9ulP.js → VPLocalSearchBox.CmWbGcGk.js} +1 -1
  14. package/dist/docs/assets/chunks/{theme.DBJvJznv.js → theme.S57OeOLA.js} +2 -2
  15. package/dist/docs/assets/{reference_channels.md.G5GysZ7e.js → reference_channels.md.CboFd5IH.js} +1 -1
  16. package/dist/docs/building-with-agents.html +3 -3
  17. package/dist/docs/concepts.html +3 -3
  18. package/dist/docs/deployment.html +3 -3
  19. package/dist/docs/evals.html +3 -3
  20. package/dist/docs/example-agents/approval-buddy.html +3 -3
  21. package/dist/docs/example-agents/benny.html +3 -3
  22. package/dist/docs/example-agents/bugbot.html +3 -3
  23. package/dist/docs/example-agents/codebase-wiki.html +3 -3
  24. package/dist/docs/example-agents/codeowners-review.html +3 -3
  25. package/dist/docs/example-agents/concierge.html +3 -3
  26. package/dist/docs/example-agents/fsd.html +3 -3
  27. package/dist/docs/example-agents/index.html +3 -3
  28. package/dist/docs/example-agents/knowledge-base.html +3 -3
  29. package/dist/docs/example-agents/oncall.html +3 -3
  30. package/dist/docs/example-agents/security-reviewer.html +3 -3
  31. package/dist/docs/example-agents/slack-agent.html +3 -3
  32. package/dist/docs/example-agents/weather-agent.html +3 -3
  33. package/dist/docs/guides/agent-to-agent.html +3 -3
  34. package/dist/docs/guides/cloud-runtime.html +3 -3
  35. package/dist/docs/guides/convert-automation.html +3 -3
  36. package/dist/docs/guides/github.html +3 -3
  37. package/dist/docs/guides/human-in-the-loop.html +3 -3
  38. package/dist/docs/guides/mcp-oauth.html +3 -3
  39. package/dist/docs/guides/opentelemetry.html +3 -3
  40. package/dist/docs/guides/slack.html +3 -3
  41. package/dist/docs/guides/webhooks.html +3 -3
  42. package/dist/docs/hashmap.json +1 -1
  43. package/dist/docs/hillclimbing.html +3 -3
  44. package/dist/docs/index.html +3 -3
  45. package/dist/docs/quickstart.html +3 -3
  46. package/dist/docs/reference/agent-config.html +3 -3
  47. package/dist/docs/reference/artifacts.html +3 -3
  48. package/dist/docs/reference/channels.html +5 -5
  49. package/dist/docs/reference/cli.html +3 -3
  50. package/dist/docs/reference/connections.html +3 -3
  51. package/dist/docs/reference/hooks.html +3 -3
  52. package/dist/docs/reference/http-api.html +3 -3
  53. package/dist/docs/reference/instructions.html +3 -3
  54. package/dist/docs/reference/playground.html +3 -3
  55. package/dist/docs/reference/project-layout.html +3 -3
  56. package/dist/docs/reference/prompt.html +3 -3
  57. package/dist/docs/reference/schedules.html +3 -3
  58. package/dist/docs/reference/sessions.html +3 -3
  59. package/dist/docs/reference/skills.html +3 -3
  60. package/dist/docs/reference/subagents.html +3 -3
  61. package/dist/docs/reference/tools.html +3 -3
  62. package/dist/docs/scaffolding-agents.html +3 -3
  63. package/dist/docs/storage.html +3 -3
  64. package/dist/docs/templates/agentic-owners.html +3 -3
  65. package/dist/docs/templates/demo.html +3 -3
  66. package/dist/docs/templates/pr-autofixer.html +3 -3
  67. package/dist/docs/templates/security-reviewer.html +3 -3
  68. package/dist/docs/templates/triage.html +3 -3
  69. package/dist/docs/troubleshooting.html +3 -3
  70. package/dist/internal/cursor-deployment-relay.d.ts +4 -4
  71. package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
  72. package/dist/internal/cursor-deployment-relay.js +33 -32
  73. package/dist/internal/discovery.d.ts.map +1 -1
  74. package/dist/internal/discovery.js +16 -1
  75. package/dist/internal/event-mapper.d.ts +7 -1
  76. package/dist/internal/event-mapper.d.ts.map +1 -1
  77. package/dist/internal/event-mapper.js +8 -8
  78. package/dist/internal/http-channel.d.ts.map +1 -1
  79. package/dist/internal/http-channel.js +1 -1
  80. package/dist/internal/mcp-endpoint.js +9 -0
  81. package/dist/internal/prompt-context.d.ts +4 -1
  82. package/dist/internal/prompt-context.d.ts.map +1 -1
  83. package/dist/internal/prompt-context.js +20 -2
  84. package/dist/internal/review-comments.js +15 -7
  85. package/dist/internal/server.js +13 -13
  86. package/dist/internal/session-engine.d.ts +28 -0
  87. package/dist/internal/session-engine.d.ts.map +1 -1
  88. package/dist/internal/session-engine.js +99 -42
  89. package/dist/internal/tool-policy.d.ts +50 -0
  90. package/dist/internal/tool-policy.d.ts.map +1 -0
  91. package/dist/internal/tool-policy.js +51 -0
  92. package/dist/playground/assets/index-CMe7imuc.css +1 -0
  93. package/dist/playground/assets/{index-Cspgk9jC.js → index-JaFLlugp.js} +45 -45
  94. package/dist/playground/index.html +2 -2
  95. package/dist/tools.d.ts +16 -2
  96. package/dist/tools.d.ts.map +1 -1
  97. package/dist/types.d.ts +67 -0
  98. package/dist/types.d.ts.map +1 -1
  99. package/docs/reference/channels.md +13 -11
  100. package/package.json +1 -1
  101. package/src/channels/deployments/deployments-channel.ts +7 -6
  102. package/src/channels/deployments/index.ts +2 -3
  103. package/src/channels/deployments/types.ts +14 -14
  104. package/src/internal/cursor-deployment-relay.ts +38 -36
  105. package/src/internal/discovery.ts +20 -0
  106. package/src/internal/event-mapper.ts +10 -1
  107. package/src/internal/http-channel.ts +1 -0
  108. package/src/internal/mcp-endpoint.ts +9 -0
  109. package/src/internal/prompt-context.ts +25 -2
  110. package/src/internal/review-comments.ts +17 -7
  111. package/src/internal/server.ts +13 -13
  112. package/src/internal/session-engine.ts +120 -31
  113. package/src/internal/tool-policy.ts +92 -0
  114. package/src/tools.ts +19 -0
  115. package/src/types.ts +73 -0
  116. package/dist/docs/assets/chunks/@localSearchIndexroot.D9uScPWb.js +0 -1
  117. package/dist/playground/assets/index-ChETdG-_.css +0 -1
  118. /package/dist/docs/assets/{reference_channels.md.G5GysZ7e.lean.js → reference_channels.md.CboFd5IH.lean.js} +0 -0
@@ -54,6 +54,7 @@ import { sessionExternalLinks } from "./session-links.js";
54
54
  import { SessionStore } from "./session-store.js";
55
55
  import { syncSkillsToStore } from "./skills-sync.js";
56
56
  import { StorageCoordinator } from "./storage-coordinator.js";
57
+ import { decideToolCall } from "./tool-policy.js";
57
58
  import { normalizeToolResult, toolCallErrorMessage } from "./tool-result.js";
58
59
  import { estimateTurnCostFromApi, roundCostUsd, } from "./turn-cost.js";
59
60
  import { TurnGovernor } from "./turn-governor.js";
@@ -124,6 +125,15 @@ export class SessionEngine {
124
125
  this.activeTurns = new Map();
125
126
  /** Context used by the cloud turn's HTTP MCP callbacks. */
126
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();
127
137
  /**
128
138
  * Delivered-nudge count per session at the last tool boundary. The carrier
129
139
  * drains out-of-process, so a growing count is how the engine learns a
@@ -530,24 +540,7 @@ export class SessionEngine {
530
540
  const now = new Date().toISOString();
531
541
  const channelState = this.initialChannelState(channelId, sendOptions.state);
532
542
  const purpose = sendOptions.purpose === "eval" ? "eval" : "live";
533
- let record = {
534
- sessionId: newSessionId(),
535
- channelId,
536
- mode: (_d = sendOptions.mode) !== null && _d !== void 0 ? _d : "chat",
537
- purpose,
538
- continuationKey: (_e = sendOptions.continuationToken) !== null && _e !== void 0 ? _e : newContinuationKey(),
539
- auth,
540
- title: sendOptions.title,
541
- channelState,
542
- workspaceOverride: sendOptions.workspaceDir,
543
- pendingWorkspaceFiles: sendOptions.workspaceFiles,
544
- pendingImages: sendOptions.images,
545
- cloudOverride: sendOptions.cloud,
546
- sdkAgentId: sendOptions.sdkAgentId,
547
- turnCount: 0,
548
- createdAt: now,
549
- updatedAt: now,
550
- };
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 });
551
544
  yield this.sessions.save(record);
552
545
  (_a = this.storage) === null || _a === void 0 ? void 0 : _a.sessionRecord(record);
553
546
  yield this.appendEvent(record.sessionId, {
@@ -1130,7 +1123,10 @@ export class SessionEngine {
1130
1123
  this.logger(`[agent-sdk] failed to record event for ${sessionId}: ${describeError(error)}`);
1131
1124
  }
1132
1125
  };
1133
- 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));
1134
1130
  // Take a turn slot before any work happens. When every slot is busy the
1135
1131
  // turn waits FIFO — durable `turn.queued` marks the wait — and a
1136
1132
  // preempting follow-up (or interrupt) resolves the wait as cancelled
@@ -1214,11 +1210,13 @@ export class SessionEngine {
1214
1210
  }
1215
1211
  }), signal, pendingImages);
1216
1212
  this.activeToolContexts.set(sessionId, { record, turnId, emit });
1213
+ this.stubbedCallIds.set(sessionId, stubbed);
1217
1214
  try {
1218
1215
  outcome = yield this.runner.runTurn(request);
1219
1216
  }
1220
1217
  finally {
1221
1218
  this.activeToolContexts.delete(sessionId);
1219
+ this.stubbedCallIds.delete(sessionId);
1222
1220
  }
1223
1221
  }
1224
1222
  catch (error) {
@@ -1752,6 +1750,7 @@ export class SessionEngine {
1752
1750
  executeServerTool(args) {
1753
1751
  return __awaiter(this, void 0, void 0, function* () {
1754
1752
  var _a;
1753
+ var _b;
1755
1754
  const execute = args.tool.definition.execute;
1756
1755
  if (args.tool.execution !== "server" || typeof execute !== "function") {
1757
1756
  return toolErrorResult(`Tool "${args.tool.name}" is not a server tool.`);
@@ -1760,7 +1759,7 @@ export class SessionEngine {
1760
1759
  if (!validation.ok) {
1761
1760
  return toolErrorResult(`Invalid input for tool "${args.tool.name}": ${validation.message}`);
1762
1761
  }
1763
- 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);
1764
1763
  const toolCtx = {
1765
1764
  toolCallId,
1766
1765
  session: this.sessionInfo(args.record),
@@ -1776,6 +1775,19 @@ export class SessionEngine {
1776
1775
  emit: args.emit,
1777
1776
  };
1778
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
+ }
1779
1791
  const needsGate = yield evaluateNeedsApproval(args.tool.definition.needsApproval, validation.value);
1780
1792
  if (needsGate) {
1781
1793
  const decision = yield this.parkForApproval({
@@ -1994,10 +2006,20 @@ export class SessionEngine {
1994
2006
  data: { calls: [{ callId, toolName, args: validation.value }] },
1995
2007
  });
1996
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
+ });
1997
2018
  const started = Date.now();
1998
2019
  this.logger(`[session] tool ${toolName} start (direct ${callId})`);
1999
2020
  let result;
2000
2021
  let isError;
2022
+ const stubbed = decision.kind === "answer";
2001
2023
  const live = record;
2002
2024
  const emit = live === undefined
2003
2025
  ? undefined
@@ -2005,26 +2027,33 @@ export class SessionEngine {
2005
2027
  void this.appendEvent(live.sessionId, Object.assign(Object.assign({}, payload), { turnId: callId })).catch(() => { });
2006
2028
  };
2007
2029
  try {
2008
- result = normalizeToolResult(yield execute(validation.value, {
2009
- toolCallId: callId,
2010
- session,
2011
- workspaceDir,
2012
- stateRoot: this.stateRoot,
2013
- host: this.hostForSession(record === null || record === void 0 ? void 0 : record.sessionId),
2014
- // Scratch calls have no real session; their facade stays unbound.
2015
- artifacts: record === undefined
2016
- ? this.artifactsApi()
2017
- : this.artifactsApi({
2018
- sessionId: record.sessionId,
2019
- turnId: callId,
2020
- }),
2021
- send: (channelId, message, sendOptions = {}) => this.send(channelId, message, Object.assign(Object.assign({}, sendOptions), { auth: sendOptions.auth !== undefined
2022
- ? sendOptions.auth
2023
- : session.auth })),
2024
- getSession: (channelId, sessionId) => this.getChannelSession(channelId, sessionId),
2025
- emit,
2026
- }));
2027
- 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
+ }
2028
2057
  }
2029
2058
  catch (error) {
2030
2059
  this.logger(`[session] tool ${toolName} failed (direct ${callId}): ${describeError(error)}`);
@@ -2051,7 +2080,8 @@ export class SessionEngine {
2051
2080
  yield this.appendEvent(record.sessionId, {
2052
2081
  type: "action.result",
2053
2082
  turnId: callId,
2054
- data: { callId, toolName, output: result, isError },
2083
+ data: Object.assign({ callId,
2084
+ toolName, output: result, isError }, (stubbed ? { stubbed: true } : {})),
2055
2085
  });
2056
2086
  }
2057
2087
  const errorMessage = isError ? toolCallErrorMessage(result) : undefined;
@@ -2062,6 +2092,33 @@ export class SessionEngine {
2062
2092
  durationMs }, (errorMessage === undefined ? {} : { errorMessage })), (record === undefined ? {} : { sessionId: record.sessionId }));
2063
2093
  });
2064
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
+ }
2065
2122
  // ==========================================================================
2066
2123
  // Events: append, dispatch to channel handlers + hooks
2067
2124
  // ==========================================================================
@@ -2609,7 +2666,7 @@ export class SessionEngine {
2609
2666
  this.coalesceDrains.has(sessionId));
2610
2667
  }
2611
2668
  sessionInfo(record) {
2612
- 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
2613
2670
  ? {}
2614
2671
  : { sdkAgentId: record.sdkAgentId }));
2615
2672
  }
@@ -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
+ }