@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.
- package/dist/channels/deployments/deployments-channel.d.ts +2 -2
- package/dist/channels/deployments/deployments-channel.d.ts.map +1 -1
- package/dist/channels/deployments/deployments-channel.js +7 -6
- package/dist/channels/deployments/index.d.ts +2 -3
- package/dist/channels/deployments/index.d.ts.map +1 -1
- package/dist/channels/deployments/index.js +2 -3
- package/dist/channels/deployments/types.d.ts +14 -14
- package/dist/channels/deployments/types.d.ts.map +1 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.6THHGRH-.js → app.DUOPbN18.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.CxCtxfDE.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.BpHB9ulP.js → VPLocalSearchBox.CmWbGcGk.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DBJvJznv.js → theme.S57OeOLA.js} +2 -2
- package/dist/docs/assets/{reference_channels.md.G5GysZ7e.js → reference_channels.md.CboFd5IH.js} +1 -1
- 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 +3 -3
- 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 +3 -3
- package/dist/docs/reference/artifacts.html +3 -3
- package/dist/docs/reference/channels.html +5 -5
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/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 +3 -3
- package/dist/internal/cursor-deployment-relay.d.ts +4 -4
- package/dist/internal/cursor-deployment-relay.d.ts.map +1 -1
- package/dist/internal/cursor-deployment-relay.js +33 -32
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +16 -1
- 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/server.js +13 -13
- package/dist/internal/session-engine.d.ts +28 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +99 -42
- 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-CMe7imuc.css +1 -0
- package/dist/playground/assets/{index-Cspgk9jC.js → index-JaFLlugp.js} +45 -45
- package/dist/playground/index.html +2 -2
- package/dist/tools.d.ts +16 -2
- package/dist/tools.d.ts.map +1 -1
- package/dist/types.d.ts +67 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/reference/channels.md +13 -11
- package/package.json +1 -1
- package/src/channels/deployments/deployments-channel.ts +7 -6
- package/src/channels/deployments/index.ts +2 -3
- package/src/channels/deployments/types.ts +14 -14
- package/src/internal/cursor-deployment-relay.ts +38 -36
- package/src/internal/discovery.ts +20 -0
- 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/server.ts +13 -13
- package/src/internal/session-engine.ts +120 -31
- package/src/internal/tool-policy.ts +92 -0
- package/src/tools.ts +19 -0
- package/src/types.ts +73 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.D9uScPWb.js +0 -1
- package/dist/playground/assets/index-ChETdG-_.css +0 -1
- /package/dist/docs/assets/{reference_channels.md.G5GysZ7e.lean.js → reference_channels.md.CboFd5IH.lean.js} +0 -0
|
@@ -22,13 +22,13 @@ const DEFAULT_EVENTS: readonly DeploymentEventType[] = ["deployment.succeeded"];
|
|
|
22
22
|
export type DeploymentRelayEvent = DeploymentEvent & { offset: string };
|
|
23
23
|
|
|
24
24
|
export interface ResolvedSubscription {
|
|
25
|
-
|
|
25
|
+
deploySourceUri: string;
|
|
26
26
|
environments: string[];
|
|
27
27
|
events: Set<DeploymentEventType>;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
30
|
interface SubscriptionStatus {
|
|
31
|
-
|
|
31
|
+
deploySourceUri: string;
|
|
32
32
|
isSubscribed: boolean;
|
|
33
33
|
detail?: string;
|
|
34
34
|
}
|
|
@@ -44,22 +44,22 @@ export interface DeploymentEventRelayOptions {
|
|
|
44
44
|
pollWaitMs?: number;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
/** Normalize the channel's `
|
|
47
|
+
/** Normalize the channel's `deploySourceUris` option into concrete subscriptions. */
|
|
48
48
|
export function resolveDeploymentSubscriptions(options: {
|
|
49
|
-
|
|
49
|
+
deploySourceUris: readonly (string | DeploymentSubscription)[];
|
|
50
50
|
environments?: readonly string[];
|
|
51
51
|
events?: readonly DeploymentEventType[];
|
|
52
52
|
}): ResolvedSubscription[] {
|
|
53
|
-
const
|
|
54
|
-
for (const entry of options.
|
|
53
|
+
const bySource = new Map<string, ResolvedSubscription>();
|
|
54
|
+
for (const entry of options.deploySourceUris) {
|
|
55
55
|
const subscription: DeploymentSubscription =
|
|
56
|
-
typeof entry === "string" ? {
|
|
57
|
-
// Case-folded only: `Deployment.
|
|
58
|
-
// the server keys subscriptions the same way.
|
|
59
|
-
const
|
|
60
|
-
if (
|
|
56
|
+
typeof entry === "string" ? { deploySourceUri: entry } : entry;
|
|
57
|
+
// Case-folded only: `Deployment.deploy_source_uri` has no documented
|
|
58
|
+
// format, and the server keys subscriptions the same way.
|
|
59
|
+
const deploySourceUri = subscription.deploySourceUri.trim().toLowerCase();
|
|
60
|
+
if (deploySourceUri === "") {
|
|
61
61
|
throw new Error(
|
|
62
|
-
`deploymentsChannel: invalid
|
|
62
|
+
`deploymentsChannel: invalid deploy source "${subscription.deploySourceUri}".`
|
|
63
63
|
);
|
|
64
64
|
}
|
|
65
65
|
const environments = [
|
|
@@ -74,14 +74,14 @@ export function resolveDeploymentSubscriptions(options: {
|
|
|
74
74
|
);
|
|
75
75
|
if (events.size === 0) {
|
|
76
76
|
throw new Error(
|
|
77
|
-
`deploymentsChannel:
|
|
77
|
+
`deploymentsChannel: deploy source "${deploySourceUri}" subscribes to no events.`
|
|
78
78
|
);
|
|
79
79
|
}
|
|
80
|
-
// Later entries for the same
|
|
81
|
-
// silently replacing it.
|
|
82
|
-
const existing =
|
|
80
|
+
// Later entries for the same deploy source widen the earlier one rather
|
|
81
|
+
// than silently replacing it.
|
|
82
|
+
const existing = bySource.get(deploySourceUri);
|
|
83
83
|
if (existing === undefined) {
|
|
84
|
-
|
|
84
|
+
bySource.set(deploySourceUri, { deploySourceUri, environments, events });
|
|
85
85
|
continue;
|
|
86
86
|
}
|
|
87
87
|
for (const event of events) existing.events.add(event);
|
|
@@ -91,9 +91,10 @@ export function resolveDeploymentSubscriptions(options: {
|
|
|
91
91
|
: [...new Set([...existing.environments, ...environments])];
|
|
92
92
|
}
|
|
93
93
|
// Empty is legal here and means "this channel wants nothing", which is how
|
|
94
|
-
// an env-configured channel stays inert without a
|
|
95
|
-
// empty list means deregister, so the host simply never
|
|
96
|
-
|
|
94
|
+
// an env-configured channel stays inert without a deploy-source list. The
|
|
95
|
+
// wire-level empty list means deregister, so the host simply never
|
|
96
|
+
// registers it.
|
|
97
|
+
return [...bySource.values()];
|
|
97
98
|
}
|
|
98
99
|
|
|
99
100
|
/** Whether one confirmed subscription wants this event. */
|
|
@@ -136,7 +137,7 @@ export async function fanOutDeploymentEvent(input: {
|
|
|
136
137
|
const matched = input.targets.filter((target) =>
|
|
137
138
|
target.subscriptions.some(
|
|
138
139
|
(subscription) =>
|
|
139
|
-
subscription.
|
|
140
|
+
subscription.deploySourceUri === event.deploySourceUri &&
|
|
140
141
|
deploymentEventMatches(subscription, event)
|
|
141
142
|
)
|
|
142
143
|
);
|
|
@@ -154,7 +155,7 @@ export async function fanOutDeploymentEvent(input: {
|
|
|
154
155
|
}
|
|
155
156
|
}
|
|
156
157
|
logger(
|
|
157
|
-
`[agent-sdk] deployment-events: ${event.eventType} ${event.
|
|
158
|
+
`[agent-sdk] deployment-events: ${event.eventType} ${event.deploySourceUri}@${event.deployVersion} (${event.environment}) → ${matched.length - failures.length}/${matched.length} channel(s)`
|
|
158
159
|
);
|
|
159
160
|
if (failures.length > 0 && failures.length === matched.length) {
|
|
160
161
|
throw new Error(
|
|
@@ -166,7 +167,7 @@ export async function fanOutDeploymentEvent(input: {
|
|
|
166
167
|
export class DeploymentEventRelay {
|
|
167
168
|
private readonly core: CursorRelayCore<DeploymentRelayEvent>;
|
|
168
169
|
private subscribed = new Map<string, ResolvedSubscription>();
|
|
169
|
-
private readonly
|
|
170
|
+
private readonly loggedSkippedSources = new Set<string>();
|
|
170
171
|
|
|
171
172
|
constructor(private readonly options: DeploymentEventRelayOptions) {
|
|
172
173
|
const adapter: RelayAdapter<DeploymentRelayEvent> = {
|
|
@@ -176,7 +177,7 @@ export class DeploymentEventRelay {
|
|
|
176
177
|
buildRegistrationBody: (consumerId) => ({
|
|
177
178
|
consumerId,
|
|
178
179
|
subscriptions: options.subscriptions.map((subscription) => ({
|
|
179
|
-
|
|
180
|
+
deploySourceUri: subscription.deploySourceUri,
|
|
180
181
|
environments: subscription.environments,
|
|
181
182
|
events: [...subscription.events],
|
|
182
183
|
})),
|
|
@@ -188,23 +189,24 @@ export class DeploymentEventRelay {
|
|
|
188
189
|
subscriptions: [],
|
|
189
190
|
}),
|
|
190
191
|
applyRegistrationResponse: (json) => {
|
|
191
|
-
const result = json as {
|
|
192
|
+
const result = json as { deploySourceUris?: SubscriptionStatus[] };
|
|
192
193
|
this.subscribed = new Map();
|
|
193
|
-
this.
|
|
194
|
+
this.loggedSkippedSources.clear();
|
|
194
195
|
const lines: string[] = [];
|
|
195
196
|
const confirmed: string[] = [];
|
|
196
|
-
for (const status of result.
|
|
197
|
+
for (const status of result.deploySourceUris ?? []) {
|
|
197
198
|
const wanted = options.subscriptions.find(
|
|
198
|
-
(subscription) =>
|
|
199
|
+
(subscription) =>
|
|
200
|
+
subscription.deploySourceUri === status.deploySourceUri
|
|
199
201
|
);
|
|
200
202
|
if (!status.isSubscribed || wanted === undefined) {
|
|
201
203
|
lines.push(
|
|
202
|
-
`not subscribed to ${status.
|
|
204
|
+
`not subscribed to ${status.deploySourceUri}: ${status.detail ?? "unknown reason"}`
|
|
203
205
|
);
|
|
204
206
|
continue;
|
|
205
207
|
}
|
|
206
|
-
this.subscribed.set(status.
|
|
207
|
-
confirmed.push(status.
|
|
208
|
+
this.subscribed.set(status.deploySourceUri, wanted);
|
|
209
|
+
confirmed.push(status.deploySourceUri);
|
|
208
210
|
}
|
|
209
211
|
if (confirmed.length > 0) {
|
|
210
212
|
const kinds = [
|
|
@@ -247,16 +249,16 @@ export class DeploymentEventRelay {
|
|
|
247
249
|
if (
|
|
248
250
|
envelope === null ||
|
|
249
251
|
typeof envelope !== "object" ||
|
|
250
|
-
typeof envelope.
|
|
252
|
+
typeof envelope.deploySourceUri !== "string"
|
|
251
253
|
) {
|
|
252
254
|
return false;
|
|
253
255
|
}
|
|
254
|
-
const subscription = this.subscribed.get(envelope.
|
|
256
|
+
const subscription = this.subscribed.get(envelope.deploySourceUri);
|
|
255
257
|
if (subscription === undefined) {
|
|
256
|
-
if (!this.
|
|
257
|
-
this.
|
|
258
|
+
if (!this.loggedSkippedSources.has(envelope.deploySourceUri)) {
|
|
259
|
+
this.loggedSkippedSources.add(envelope.deploySourceUri);
|
|
258
260
|
this.options.logger(
|
|
259
|
-
`[agent-sdk] deployment-events: skipping events for ${envelope.
|
|
261
|
+
`[agent-sdk] deployment-events: skipping events for ${envelope.deploySourceUri}: not in this host's subscription (another consumer of this API key likely subscribed it)`
|
|
260
262
|
);
|
|
261
263
|
}
|
|
262
264
|
return false;
|
|
@@ -59,6 +59,7 @@ import {
|
|
|
59
59
|
type SkillDefinition,
|
|
60
60
|
type SkillFileSource,
|
|
61
61
|
type ToolDefinition,
|
|
62
|
+
type ToolEffect,
|
|
62
63
|
toPublicCloudInfo,
|
|
63
64
|
} from "../types.js";
|
|
64
65
|
import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
|
|
@@ -870,6 +871,7 @@ async function loadTools(
|
|
|
870
871
|
const execution = tool.execution === "agent" ? "agent" : "server";
|
|
871
872
|
const needsApprovalConfigured =
|
|
872
873
|
tool.needsApproval === true || typeof tool.needsApproval === "function";
|
|
874
|
+
const effect = declaredEffect(tool);
|
|
873
875
|
if (execution === "agent") {
|
|
874
876
|
if (isBlank(tool.script)) {
|
|
875
877
|
ctx.diagnostics.push({
|
|
@@ -935,6 +937,7 @@ async function loadTools(
|
|
|
935
937
|
...(outputSchema === undefined ? {} : { outputSchema }),
|
|
936
938
|
execution,
|
|
937
939
|
needsApproval: needsApprovalConfigured,
|
|
940
|
+
...(effect === undefined ? {} : { effect }),
|
|
938
941
|
...(execution === "agent" && tool.script !== undefined
|
|
939
942
|
? { script: tool.script }
|
|
940
943
|
: {}),
|
|
@@ -944,6 +947,23 @@ async function loadTools(
|
|
|
944
947
|
return tools;
|
|
945
948
|
}
|
|
946
949
|
|
|
950
|
+
/**
|
|
951
|
+
* Static view of a tool's `effect` for the manifest: the literal when it is
|
|
952
|
+
* one, `"dynamic"` for a per-call predicate, `undefined` when undeclared. A
|
|
953
|
+
* malformed value is left undeclared rather than guessed — undeclared is the
|
|
954
|
+
* safe state (a dry run stubs it), where a guess could be the unsafe one.
|
|
955
|
+
*/
|
|
956
|
+
function declaredEffect(
|
|
957
|
+
tool: ToolDefinition
|
|
958
|
+
): ToolEffect | "dynamic" | undefined {
|
|
959
|
+
if (typeof tool.effect === "function") {
|
|
960
|
+
return "dynamic";
|
|
961
|
+
}
|
|
962
|
+
return tool.effect === "read" || tool.effect === "write"
|
|
963
|
+
? tool.effect
|
|
964
|
+
: undefined;
|
|
965
|
+
}
|
|
966
|
+
|
|
947
967
|
// ============================================================================
|
|
948
968
|
// Skills
|
|
949
969
|
// ============================================================================
|
|
@@ -88,7 +88,15 @@ export class TurnEventMapper {
|
|
|
88
88
|
/** Per-parent-call accumulators for nested subagent streams. */
|
|
89
89
|
private readonly nested = new Map<string, NestedAccumulators>();
|
|
90
90
|
|
|
91
|
-
|
|
91
|
+
/**
|
|
92
|
+
* @param isStubbed Reports whether a finished tool call was answered by the
|
|
93
|
+
* host rather than executed. The harness reports an answered call exactly
|
|
94
|
+
* like a real one, so the engine is the only place that knows.
|
|
95
|
+
*/
|
|
96
|
+
constructor(
|
|
97
|
+
private readonly emit: (payload: SessionEventPayload) => void,
|
|
98
|
+
private readonly isStubbed: (callId: string) => boolean = () => false
|
|
99
|
+
) {}
|
|
92
100
|
|
|
93
101
|
/** Per-turn token usage summed across model steps. */
|
|
94
102
|
get usage(): TurnUsage | undefined {
|
|
@@ -303,6 +311,7 @@ export class TurnEventMapper {
|
|
|
303
311
|
toolName: description.toolName,
|
|
304
312
|
output: result.output,
|
|
305
313
|
isError: result.isError,
|
|
314
|
+
...(this.isStubbed(callId) ? { stubbed: true as const } : {}),
|
|
306
315
|
parentCallId,
|
|
307
316
|
},
|
|
308
317
|
});
|
|
@@ -109,6 +109,7 @@ export function buildHttpChannelRoutes(args: {
|
|
|
109
109
|
...(body.state === undefined ? {} : { state: body.state as JsonValue }),
|
|
110
110
|
...(typeof body.title === "string" ? { title: body.title } : {}),
|
|
111
111
|
...(body.purpose === "eval" ? { purpose: "eval" as const } : {}),
|
|
112
|
+
...(body.dryRun === true ? { dryRun: true } : {}),
|
|
112
113
|
...(workspaceFiles === undefined ? {} : { workspaceFiles }),
|
|
113
114
|
...(cloud === undefined ? {} : { cloud }),
|
|
114
115
|
});
|
|
@@ -312,6 +312,15 @@ function buildConnectionBridgeMcpServer(
|
|
|
312
312
|
if (reason !== undefined) {
|
|
313
313
|
return errorResult(reason);
|
|
314
314
|
}
|
|
315
|
+
// The one tool path that never reaches `executeServerTool`, so it asks
|
|
316
|
+
// the policy itself.
|
|
317
|
+
const answer = engine.bridgedCallAnswer({
|
|
318
|
+
sessionId: sessionId ?? "",
|
|
319
|
+
toolName: request.params.name,
|
|
320
|
+
});
|
|
321
|
+
if (answer.answered) {
|
|
322
|
+
return sdkResultToMcpResult(answer.result);
|
|
323
|
+
}
|
|
315
324
|
try {
|
|
316
325
|
const result = await engine.host.mcp.callTool(
|
|
317
326
|
connectionName,
|
|
@@ -39,17 +39,40 @@ export interface SessionContextInput {
|
|
|
39
39
|
*/
|
|
40
40
|
channelKind?: string;
|
|
41
41
|
environment: PromptEnvironment;
|
|
42
|
-
/**
|
|
42
|
+
/**
|
|
43
|
+
* Injectable clock for tests; defaults to the mocked prompt time when
|
|
44
|
+
* `CURSOR_SDK_MOCK_PROMPT_TIME` is set, then to now.
|
|
45
|
+
*/
|
|
43
46
|
now?: Date;
|
|
44
47
|
}
|
|
45
48
|
|
|
49
|
+
/**
|
|
50
|
+
* Development-only override for the wall clock rendered into prompt content.
|
|
51
|
+
* Same variable the Cursor SDK harness reads for its prompt clock (the
|
|
52
|
+
* user-message <timestamp> prefix and user_info date), so one knob keeps the
|
|
53
|
+
* session context block and the harness-rendered clock consistent. Read on
|
|
54
|
+
* every render so a simulation harness can advance the instant between
|
|
55
|
+
* sessions. Never affects runtime clocks (schedules, reminders) or tools.
|
|
56
|
+
*/
|
|
57
|
+
const MOCK_PROMPT_TIME_ENV = "CURSOR_SDK_MOCK_PROMPT_TIME";
|
|
58
|
+
|
|
59
|
+
function readMockPromptTimeFromEnv(): Date | undefined {
|
|
60
|
+
const raw = process.env[MOCK_PROMPT_TIME_ENV];
|
|
61
|
+
if (raw === undefined || raw === "") {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
const parsed = new Date(raw);
|
|
65
|
+
return Number.isNaN(parsed.getTime()) ? undefined : parsed;
|
|
66
|
+
}
|
|
67
|
+
|
|
46
68
|
export function buildSessionContextBlock(input: SessionContextInput): string {
|
|
69
|
+
const now = input.now ?? readMockPromptTimeFromEnv() ?? new Date();
|
|
47
70
|
const lines = [
|
|
48
71
|
"<agentkit_context>",
|
|
49
72
|
`session: ${input.sessionId}`,
|
|
50
73
|
`channel: ${input.channelId}`,
|
|
51
74
|
`environment: ${input.environment}`,
|
|
52
|
-
`time: ${
|
|
75
|
+
`time: ${now.toISOString()}`,
|
|
53
76
|
"</agentkit_context>",
|
|
54
77
|
];
|
|
55
78
|
const presence = channelPresenceLine(input);
|
|
@@ -690,10 +690,13 @@ async function postReviewViaHostGithub(
|
|
|
690
690
|
/**
|
|
691
691
|
* Ids of the inline comments a just-created review owns.
|
|
692
692
|
*
|
|
693
|
-
* `pulls.createReview` returns the review, not its comments
|
|
694
|
-
*
|
|
695
|
-
*
|
|
696
|
-
*
|
|
693
|
+
* `pulls.createReview` returns the review, not its comments.
|
|
694
|
+
* `listCommentsForReview` either omits those rows or returns them with
|
|
695
|
+
* `line` / `original_line` unset, so the line filter drops every id, the
|
|
696
|
+
* store records no findings, and the next run cannot judge or resolve
|
|
697
|
+
* those threads. Read the PR-wide list instead and keep only this
|
|
698
|
+
* review's rows. Newest-first so a busy PR's older comments cannot push
|
|
699
|
+
* ours past the page cap.
|
|
697
700
|
* Best-effort: any failure yields an empty list, which costs the next run
|
|
698
701
|
* its prior findings rather than risking a wrong id. Never throws — the
|
|
699
702
|
* review is already posted by this point.
|
|
@@ -714,22 +717,29 @@ async function readPostedComments(args: {
|
|
|
714
717
|
const posted: PostedReviewComment[] = [];
|
|
715
718
|
for (let page = 1; page <= REVIEW_COMMENTS_MAX_PAGES; page++) {
|
|
716
719
|
args.signal?.throwIfAborted();
|
|
717
|
-
const response = await args.octokit.rest.pulls.
|
|
720
|
+
const response = await args.octokit.rest.pulls.listReviewComments({
|
|
718
721
|
owner: args.owner,
|
|
719
722
|
repo: args.repo,
|
|
720
723
|
pull_number: args.prNumber,
|
|
721
|
-
|
|
724
|
+
sort: "created",
|
|
725
|
+
direction: "desc",
|
|
722
726
|
per_page: REVIEW_COMMENTS_PAGE_SIZE,
|
|
723
727
|
page,
|
|
724
728
|
});
|
|
725
729
|
for (const comment of response.data) {
|
|
730
|
+
if (comment.pull_request_review_id !== args.reviewId) {
|
|
731
|
+
continue;
|
|
732
|
+
}
|
|
726
733
|
const line = comment.original_line ?? comment.line;
|
|
727
734
|
if (line === null || line === undefined) {
|
|
728
735
|
continue;
|
|
729
736
|
}
|
|
730
737
|
posted.push({ id: comment.id, file: comment.path, line });
|
|
731
738
|
}
|
|
732
|
-
if (
|
|
739
|
+
if (
|
|
740
|
+
posted.length >= args.expected ||
|
|
741
|
+
response.data.length < REVIEW_COMMENTS_PAGE_SIZE
|
|
742
|
+
) {
|
|
733
743
|
break;
|
|
734
744
|
}
|
|
735
745
|
}
|
package/src/internal/server.ts
CHANGED
|
@@ -2291,7 +2291,7 @@ function collectCursorDeploymentTargets(
|
|
|
2291
2291
|
}
|
|
2292
2292
|
const subscriptions = resolveDeploymentSubscriptions(options);
|
|
2293
2293
|
if (subscriptions.length === 0) {
|
|
2294
|
-
// Declared the channel but named no
|
|
2294
|
+
// Declared the channel but named no deploy sources — usually an unset
|
|
2295
2295
|
// env var. Say so rather than starting a relay that receives nothing.
|
|
2296
2296
|
skipped.push(`${runtime.slug || runtime.project.name}/${channel.id}`);
|
|
2297
2297
|
continue;
|
|
@@ -2305,7 +2305,7 @@ function collectCursorDeploymentTargets(
|
|
|
2305
2305
|
/**
|
|
2306
2306
|
* Cursor deployment-events relay: automatic when a mounted agent authored
|
|
2307
2307
|
* `deploymentsChannel({ ... })`. One relay per process reads
|
|
2308
|
-
* `/v0/deployment-events` for the union of every channel's
|
|
2308
|
+
* `/v0/deployment-events` for the union of every channel's deploy sources, and
|
|
2309
2309
|
* each event is routed to the channels that asked for it — the same ownership
|
|
2310
2310
|
* `startCursorSlackRelay` and `startCursorEventRelay` have.
|
|
2311
2311
|
*/
|
|
@@ -2320,7 +2320,7 @@ function startCursorDeploymentRelay(input: {
|
|
|
2320
2320
|
const targets = collectCursorDeploymentTargets(runtimes, skipped);
|
|
2321
2321
|
if (skipped.length > 0) {
|
|
2322
2322
|
logger(
|
|
2323
|
-
`[agent-sdk] deployment-events: no
|
|
2323
|
+
`[agent-sdk] deployment-events: no deploy sources declared on ${skipped.join(", ")}; those channels receive nothing`
|
|
2324
2324
|
);
|
|
2325
2325
|
}
|
|
2326
2326
|
if (targets.length === 0) {
|
|
@@ -2328,19 +2328,19 @@ function startCursorDeploymentRelay(input: {
|
|
|
2328
2328
|
}
|
|
2329
2329
|
if (accountKey === undefined) {
|
|
2330
2330
|
throw new Error(
|
|
2331
|
-
`This project has channels subscribed to deployment events (deploymentsChannel({
|
|
2331
|
+
`This project has channels subscribed to deployment events (deploymentsChannel({ deploySourceUris })) but the host is not signed in to Cursor. ${SIGN_IN_HINT}`
|
|
2332
2332
|
);
|
|
2333
2333
|
}
|
|
2334
|
-
// Union across channels: one registration per
|
|
2335
|
-
// however many channels named it. Per-channel environment and
|
|
2336
|
-
// filtering happens at dispatch.
|
|
2337
|
-
const
|
|
2334
|
+
// Union across channels: one registration per deploy source for the whole
|
|
2335
|
+
// process, however many channels named it. Per-channel environment and
|
|
2336
|
+
// event-type filtering happens at dispatch.
|
|
2337
|
+
const bySource = new Map<string, ResolvedSubscription>();
|
|
2338
2338
|
for (const target of targets) {
|
|
2339
2339
|
for (const subscription of target.subscriptions) {
|
|
2340
|
-
const existing =
|
|
2340
|
+
const existing = bySource.get(subscription.deploySourceUri);
|
|
2341
2341
|
if (existing === undefined) {
|
|
2342
|
-
|
|
2343
|
-
|
|
2342
|
+
bySource.set(subscription.deploySourceUri, {
|
|
2343
|
+
deploySourceUri: subscription.deploySourceUri,
|
|
2344
2344
|
environments: [...subscription.environments],
|
|
2345
2345
|
events: new Set(subscription.events),
|
|
2346
2346
|
});
|
|
@@ -2369,7 +2369,7 @@ function startCursorDeploymentRelay(input: {
|
|
|
2369
2369
|
baseUrl: cursorExternalApiUrl(),
|
|
2370
2370
|
apiKey: accountKey.apiKey,
|
|
2371
2371
|
stateDir: join(baseStateRoot, "deployment-events"),
|
|
2372
|
-
subscriptions: [...
|
|
2372
|
+
subscriptions: [...bySource.values()],
|
|
2373
2373
|
logger,
|
|
2374
2374
|
dispatch: async (event) =>
|
|
2375
2375
|
fanOutDeploymentEvent({
|
|
@@ -2392,7 +2392,7 @@ function startCursorDeploymentRelay(input: {
|
|
|
2392
2392
|
});
|
|
2393
2393
|
relay.start();
|
|
2394
2394
|
logger(
|
|
2395
|
-
`[agent-sdk] deployment-events: relaying ${
|
|
2395
|
+
`[agent-sdk] deployment-events: relaying ${bySource.size} deploy-source subscription(s) to ${targets.length} channel(s)`
|
|
2396
2396
|
);
|
|
2397
2397
|
return relay;
|
|
2398
2398
|
}
|