@cursor/july 0.1.17 → 0.1.18
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/slack/cursor-account.d.ts.map +1 -1
- package/dist/channels/slack/cursor-account.js +1 -1
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +4 -1
- package/dist/channels/slack/inbound.d.ts.map +1 -1
- package/dist/channels/slack/inbound.js +6 -1
- package/dist/channels/slack/index.d.ts +1 -0
- package/dist/channels/slack/index.d.ts.map +1 -1
- package/dist/channels/slack/index.js +1 -0
- package/dist/channels/slack/init.d.ts.map +1 -1
- package/dist/channels/slack/init.js +4 -1
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +1 -0
- package/dist/channels/slack/manifest.d.ts +10 -0
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +33 -14
- package/dist/channels/slack/message-body.d.ts +13 -0
- package/dist/channels/slack/message-body.d.ts.map +1 -0
- package/dist/channels/slack/message-body.js +82 -0
- package/dist/channels/slack/thread-context.d.ts +0 -2
- package/dist/channels/slack/thread-context.d.ts.map +1 -1
- package/dist/channels/slack/thread-context.js +1 -65
- package/dist/channels/slack/types.d.ts +16 -1
- package/dist/channels/slack/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.BbEXr2KO.js → app.BR5EdVSq.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BOzkeUi8.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.DBEi4HDp.js → VPLocalSearchBox.CNCzfUKC.js} +1 -1
- package/dist/docs/assets/chunks/{theme.DGfptcHw.js → theme.CwKvMSWv.js} +2 -2
- package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.js → example-agents_security-reviewer.md.Dhj_m7_B.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 +5 -5
- 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/github.html +3 -3
- package/dist/docs/guides/human-in-the-loop.html +3 -3
- package/dist/docs/guides/mcp-oauth.html +3 -3
- package/dist/docs/guides/slack.html +3 -3
- package/dist/docs/guides/webhooks.html +3 -3
- package/dist/docs/hashmap.json +1 -1
- package/dist/docs/hillclimbing.html +3 -3
- package/dist/docs/index.html +3 -3
- package/dist/docs/quickstart.html +3 -3
- package/dist/docs/reference/agent-config.html +3 -3
- package/dist/docs/reference/channels.html +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +3 -3
- package/dist/docs/reference/hooks.html +3 -3
- package/dist/docs/reference/http-api.html +3 -3
- package/dist/docs/reference/instructions.html +3 -3
- package/dist/docs/reference/playground.html +3 -3
- package/dist/docs/reference/project-layout.html +3 -3
- package/dist/docs/reference/prompt.html +3 -3
- package/dist/docs/reference/schedules.html +3 -3
- package/dist/docs/reference/sessions.html +3 -3
- package/dist/docs/reference/skills.html +3 -3
- package/dist/docs/reference/subagents.html +3 -3
- package/dist/docs/reference/tools.html +3 -3
- package/dist/docs/scaffolding-agents.html +3 -3
- package/dist/docs/storage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/internal/builtin-tools/index.d.ts +10 -0
- package/dist/internal/builtin-tools/index.d.ts.map +1 -0
- package/dist/internal/builtin-tools/index.js +27 -0
- package/dist/internal/builtin-tools/reminders.d.ts +9 -0
- package/dist/internal/builtin-tools/reminders.d.ts.map +1 -0
- package/dist/internal/builtin-tools/reminders.js +138 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +36 -15
- package/dist/internal/cursor-event-relay.d.ts +30 -0
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +37 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +41 -0
- package/dist/internal/reminder-runner.d.ts +7 -0
- package/dist/internal/reminder-runner.d.ts.map +1 -1
- package/dist/internal/reminder-runner.js +21 -0
- package/dist/internal/sdk-runner.d.ts +15 -0
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +113 -13
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +1 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +9 -0
- package/dist/internal/slack-provision-client.d.ts +6 -3
- package/dist/internal/slack-provision-client.d.ts.map +1 -1
- package/dist/internal/slack-provision-client.js +5 -3
- package/dist/playground/assets/{index-DNqirsrK.js → index-DRjS5klQ.js} +42 -42
- package/dist/playground/assets/index-TCDs-UhZ.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +28 -0
- package/dist/types.d.ts.map +1 -1
- package/docs/example-agents/security-reviewer.md +1 -1
- package/package.json +1 -1
- package/skills/setup-slack/SKILL.md +4 -0
- package/src/channels/slack/cursor-account.ts +1 -0
- package/src/channels/slack/dispatch.ts +3 -1
- package/src/channels/slack/inbound.ts +6 -1
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/init.ts +3 -0
- package/src/channels/slack/interactive.ts +1 -0
- package/src/channels/slack/manifest.ts +34 -6
- package/src/channels/slack/message-body.ts +90 -0
- package/src/channels/slack/thread-context.ts +1 -69
- package/src/channels/slack/types.ts +16 -1
- package/src/internal/builtin-tools/index.ts +40 -0
- package/src/internal/builtin-tools/reminders.ts +158 -0
- package/src/internal/cli-slack.ts +38 -14
- package/src/internal/cursor-event-relay.ts +67 -0
- package/src/internal/discovery.ts +52 -0
- package/src/internal/reminder-runner.ts +30 -0
- package/src/internal/sdk-runner.ts +131 -13
- package/src/internal/server.ts +2 -0
- package/src/internal/session-engine.ts +14 -0
- package/src/internal/slack-provision-client.ts +7 -3
- package/src/types.ts +33 -0
- package/dist/docs/assets/chunks/@localSearchIndexroot.DCBsrfwP.js +0 -1
- package/dist/playground/assets/index-CidizGZv.css +0 -1
- /package/dist/docs/assets/{example-agents_security-reviewer.md.BZSNycMf.lean.js → example-agents_security-reviewer.md.Dhj_m7_B.lean.js} +0 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in tools (`AgentConfig.builtinTools`): framework-provided
|
|
3
|
+
* model-facing tools, materialized as ordinary server tools at discovery
|
|
4
|
+
* time so turns, direct calls, `info`, and the playground treat them
|
|
5
|
+
* exactly like authored tools.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type {
|
|
9
|
+
BuiltinToolsConfig,
|
|
10
|
+
DiscoveredTool,
|
|
11
|
+
ToolDefinition,
|
|
12
|
+
} from "../../types.js";
|
|
13
|
+
import { toolInputJsonSchema } from "../json-schema.js";
|
|
14
|
+
import { reminderTools } from "./reminders.js";
|
|
15
|
+
|
|
16
|
+
/** Resolve the enabled built-in tools, in stable catalog order. */
|
|
17
|
+
export function builtinTools(
|
|
18
|
+
config: BuiltinToolsConfig | undefined
|
|
19
|
+
): DiscoveredTool[] {
|
|
20
|
+
const tools: DiscoveredTool[] = [];
|
|
21
|
+
if (config?.reminders === true) {
|
|
22
|
+
tools.push(...toDiscovered(reminderTools));
|
|
23
|
+
}
|
|
24
|
+
return tools;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function toDiscovered(
|
|
28
|
+
byName: Record<string, ToolDefinition>
|
|
29
|
+
): DiscoveredTool[] {
|
|
30
|
+
return Object.entries(byName).map(([name, definition]) => ({
|
|
31
|
+
name,
|
|
32
|
+
description: definition.description,
|
|
33
|
+
inputSchema: toolInputJsonSchema(definition.inputSchema),
|
|
34
|
+
execution: "server",
|
|
35
|
+
needsApproval:
|
|
36
|
+
definition.needsApproval === true ||
|
|
37
|
+
typeof definition.needsApproval === "function",
|
|
38
|
+
definition,
|
|
39
|
+
}));
|
|
40
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in reminder tools (`builtinTools: { reminders: true }`): durable
|
|
3
|
+
* wakes bound to the current conversation over `host.reminders`. The
|
|
4
|
+
* framework's reminder fire prompt tells the model to call
|
|
5
|
+
* `reminders_cancel` by name, so these tool names must not change.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
import { defineTool } from "../../tools.js";
|
|
10
|
+
import type {
|
|
11
|
+
ReminderHostApi,
|
|
12
|
+
ReminderInfo,
|
|
13
|
+
ToolContext,
|
|
14
|
+
ToolDefinition,
|
|
15
|
+
} from "../../types.js";
|
|
16
|
+
import { parseDuration } from "../duration.js";
|
|
17
|
+
|
|
18
|
+
/** Floor for model-armed recurring wakes: each fire spends a model turn. */
|
|
19
|
+
const MIN_EVERY_MS = 60_000;
|
|
20
|
+
|
|
21
|
+
function api(ctx: ToolContext): ReminderHostApi {
|
|
22
|
+
const reminders = ctx.host.reminders;
|
|
23
|
+
if (reminders === undefined) {
|
|
24
|
+
throw new Error("Reminders are disabled on this host.");
|
|
25
|
+
}
|
|
26
|
+
return reminders;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function conversation(ctx: ToolContext): {
|
|
30
|
+
channelId: string;
|
|
31
|
+
continuationToken: string;
|
|
32
|
+
} {
|
|
33
|
+
const token = ctx.session.continuationKey;
|
|
34
|
+
if (token == null) {
|
|
35
|
+
throw new Error(
|
|
36
|
+
"This session is not bound to a followable conversation; reminders cannot wake it."
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return { channelId: ctx.session.channelId, continuationToken: token };
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Reminders visible to this conversation (tokens are channel-local). */
|
|
43
|
+
async function listMine(
|
|
44
|
+
ctx: ToolContext,
|
|
45
|
+
status?: ReminderInfo["status"]
|
|
46
|
+
): Promise<ReminderInfo[]> {
|
|
47
|
+
const conv = conversation(ctx);
|
|
48
|
+
const all = await api(ctx).list({
|
|
49
|
+
status,
|
|
50
|
+
continuationToken: conv.continuationToken,
|
|
51
|
+
});
|
|
52
|
+
return all.filter((r) => r.channelId === conv.channelId);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function toResult(r: ReminderInfo) {
|
|
56
|
+
return {
|
|
57
|
+
reminderId: r.id,
|
|
58
|
+
purpose: r.purpose,
|
|
59
|
+
schedule: `${r.scheduleKind} ${r.scheduleValue}`,
|
|
60
|
+
nextFireAt: r.nextFireAt,
|
|
61
|
+
until: r.untilPrompt,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const create = defineTool({
|
|
66
|
+
description:
|
|
67
|
+
"Set a reminder for yourself in this conversation. When it fires, your prompt returns here as a follow-up turn. Use it to re-check state that needs time to settle. One-shot (delay) or recurring (every).",
|
|
68
|
+
inputSchema: z.object({
|
|
69
|
+
prompt: z
|
|
70
|
+
.string()
|
|
71
|
+
.min(1)
|
|
72
|
+
.describe(
|
|
73
|
+
"What to do when you wake. Keep it generic ('re-check CI and report only if the status changed') and re-read live state on wake instead of replaying stale details."
|
|
74
|
+
),
|
|
75
|
+
delay: z
|
|
76
|
+
.string()
|
|
77
|
+
.optional()
|
|
78
|
+
.describe(
|
|
79
|
+
'One-shot: wake once after this duration, e.g. "20m" or "2h". Provide exactly one of delay or every.'
|
|
80
|
+
),
|
|
81
|
+
every: z
|
|
82
|
+
.string()
|
|
83
|
+
.optional()
|
|
84
|
+
.describe(
|
|
85
|
+
'Recurring: wake at this interval, e.g. "30m", until cancelled or the stop condition is met. Provide exactly one of delay or every.'
|
|
86
|
+
),
|
|
87
|
+
purpose: z
|
|
88
|
+
.string()
|
|
89
|
+
.min(1)
|
|
90
|
+
.max(64)
|
|
91
|
+
.optional()
|
|
92
|
+
.describe(
|
|
93
|
+
'Short slug naming the reminder, e.g. "ci_recheck". Defaults to "follow_up".'
|
|
94
|
+
),
|
|
95
|
+
until: z
|
|
96
|
+
.string()
|
|
97
|
+
.optional()
|
|
98
|
+
.describe(
|
|
99
|
+
"Recurring only: plain-language stop condition you will honor by calling reminders_cancel, e.g. 'Cancel once CI is green or the PR is merged.'"
|
|
100
|
+
),
|
|
101
|
+
}),
|
|
102
|
+
async execute(input, ctx) {
|
|
103
|
+
if ((input.delay !== undefined) === (input.every !== undefined)) {
|
|
104
|
+
throw new Error("Provide exactly one of delay or every.");
|
|
105
|
+
}
|
|
106
|
+
if (
|
|
107
|
+
input.every !== undefined &&
|
|
108
|
+
parseDuration(input.every) < MIN_EVERY_MS
|
|
109
|
+
) {
|
|
110
|
+
throw new Error('Recurring reminders must use every >= "1m".');
|
|
111
|
+
}
|
|
112
|
+
const conv = conversation(ctx);
|
|
113
|
+
const reminder = await api(ctx).create({
|
|
114
|
+
purpose: input.purpose ?? "follow_up",
|
|
115
|
+
channelId: conv.channelId,
|
|
116
|
+
continuationToken: conv.continuationToken,
|
|
117
|
+
prompt: input.prompt,
|
|
118
|
+
delay: input.delay,
|
|
119
|
+
every: input.every,
|
|
120
|
+
until: input.until,
|
|
121
|
+
});
|
|
122
|
+
return toResult(reminder);
|
|
123
|
+
},
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const list = defineTool({
|
|
127
|
+
description:
|
|
128
|
+
"List the armed reminders bound to this conversation. Check before arming duplicates.",
|
|
129
|
+
inputSchema: z.object({}),
|
|
130
|
+
async execute(_input, ctx) {
|
|
131
|
+
return { reminders: (await listMine(ctx, "armed")).map(toResult) };
|
|
132
|
+
},
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const cancel = defineTool({
|
|
136
|
+
description:
|
|
137
|
+
"Cancel one of this conversation's reminders by id, the moment its stop condition is met or it is no longer needed.",
|
|
138
|
+
inputSchema: z.object({
|
|
139
|
+
id: z
|
|
140
|
+
.string()
|
|
141
|
+
.min(1)
|
|
142
|
+
.describe("Reminder id from reminders_create or reminders_list."),
|
|
143
|
+
}),
|
|
144
|
+
async execute(input, ctx) {
|
|
145
|
+
const mine = (await listMine(ctx)).find((r) => r.id === input.id);
|
|
146
|
+
if (mine === undefined) {
|
|
147
|
+
throw new Error(`No reminder "${input.id}" in this conversation.`);
|
|
148
|
+
}
|
|
149
|
+
const cancelled = await api(ctx).cancel(input.id);
|
|
150
|
+
return { reminderId: input.id, status: cancelled?.status ?? "cancelled" };
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
export const reminderTools: Record<string, ToolDefinition> = {
|
|
155
|
+
reminders_create: create,
|
|
156
|
+
reminders_list: list,
|
|
157
|
+
reminders_cancel: cancel,
|
|
158
|
+
};
|
|
@@ -198,6 +198,9 @@ async function runManifest(options: SlackCliOptions): Promise<number> {
|
|
|
198
198
|
const name = options.name ?? basename(dir);
|
|
199
199
|
const pair = buildSlackManifestPair({
|
|
200
200
|
name,
|
|
201
|
+
// These files are pasted into api.slack.com by hand, so they must not
|
|
202
|
+
// carry the manager-token-only managed_app_settings block.
|
|
203
|
+
managedByCursor: false,
|
|
201
204
|
...(options.channelPosts === true ? { channelPosts: true } : {}),
|
|
202
205
|
});
|
|
203
206
|
|
|
@@ -488,6 +491,14 @@ interface ConnectWaitTarget {
|
|
|
488
491
|
notConnectedAt?: string;
|
|
489
492
|
}
|
|
490
493
|
|
|
494
|
+
/** "Name (T0123ABCD)", or the bare id when the backend has no name. */
|
|
495
|
+
function formatWorkspace(workspace: SlackConnectedWorkspace): string {
|
|
496
|
+
const name = workspace.slackTeamName ?? "";
|
|
497
|
+
return name === ""
|
|
498
|
+
? workspace.slackTeamId
|
|
499
|
+
: `${name} (${workspace.slackTeamId})`;
|
|
500
|
+
}
|
|
501
|
+
|
|
491
502
|
function matchConnectedWorkspace(
|
|
492
503
|
workspaces: SlackConnectedWorkspace[],
|
|
493
504
|
wait: ConnectWaitTarget
|
|
@@ -522,7 +533,9 @@ async function runConnectFlow(
|
|
|
522
533
|
return renderSlackApiError(error, ctx.err);
|
|
523
534
|
}
|
|
524
535
|
ctx.err(
|
|
525
|
-
`\nConnect your Slack workspace to Cursor — approve in the browser:\n\n ${url}\n\n
|
|
536
|
+
`\nConnect your Slack workspace to Cursor — approve in the browser:\n\n ${url}\n\n` +
|
|
537
|
+
`After approving in Slack you land on cursor.com, which links the workspace to your Cursor account — sign in there if the browser is not already signed in as you.\n\n` +
|
|
538
|
+
`(waiting for the Slack consent to complete, up to ${Math.round(ctx.pollTimeoutMs / 60_000)} minutes)\n`
|
|
526
539
|
);
|
|
527
540
|
ctx.openBrowserImpl(url);
|
|
528
541
|
const deadline = Date.now() + ctx.pollTimeoutMs;
|
|
@@ -559,7 +572,7 @@ async function runConnectFlow(
|
|
|
559
572
|
}
|
|
560
573
|
if (Date.now() >= deadline) {
|
|
561
574
|
ctx.err(
|
|
562
|
-
`Timed out waiting for the Slack workspace connection. The consent link stays valid for ~10 minutes — approve it
|
|
575
|
+
`Timed out waiting for the Slack workspace connection. The consent link stays valid for ~10 minutes — approve it, finish the sign-in on cursor.com, then re-run \`${CLI} slack create\`.\n`
|
|
563
576
|
);
|
|
564
577
|
return 1;
|
|
565
578
|
}
|
|
@@ -611,7 +624,7 @@ export async function cmdSlackCreate(
|
|
|
611
624
|
status.workspaces
|
|
612
625
|
.map(
|
|
613
626
|
(candidate) =>
|
|
614
|
-
` - ${candidate
|
|
627
|
+
` - ${formatWorkspace(candidate)} (Slack user ${candidate.slackUserId}, connected ${candidate.connectedAt})\n`
|
|
615
628
|
)
|
|
616
629
|
.join("") +
|
|
617
630
|
`Pass --slack-team <id> to pick one.\n`
|
|
@@ -647,8 +660,8 @@ export async function cmdSlackCreate(
|
|
|
647
660
|
});
|
|
648
661
|
const manifestJson = JSON.stringify(manifest);
|
|
649
662
|
|
|
650
|
-
// One connect-and-retry for not_connected / insufficient_scopes
|
|
651
|
-
//
|
|
663
|
+
// One connect-and-retry for not_connected / insufficient_scopes /
|
|
664
|
+
// manager_reauth_required (all fixed by a fresh consent grant).
|
|
652
665
|
let result: SlackAppProvisionResult;
|
|
653
666
|
let connectRetried = false;
|
|
654
667
|
for (;;) {
|
|
@@ -667,12 +680,14 @@ export async function cmdSlackCreate(
|
|
|
667
680
|
if (
|
|
668
681
|
error instanceof SlackProvisionApiError &&
|
|
669
682
|
(error.code === "not_connected" ||
|
|
670
|
-
error.code === "insufficient_scopes"
|
|
683
|
+
error.code === "insufficient_scopes" ||
|
|
684
|
+
error.code === "manager_reauth_required") &&
|
|
671
685
|
!connectRetried
|
|
672
686
|
) {
|
|
673
687
|
connectRetried = true;
|
|
674
|
-
//
|
|
675
|
-
// the poll waits for a fresh grant instead of re-matching
|
|
688
|
+
// All three codes arrive holding a row that GET /status already
|
|
689
|
+
// lists, so the poll waits for a fresh grant instead of re-matching
|
|
690
|
+
// that row.
|
|
676
691
|
const wait: ConnectWaitTarget = {
|
|
677
692
|
slackTeamId: workspace.slackTeamId,
|
|
678
693
|
notConnectedAt: workspace.connectedAt,
|
|
@@ -681,6 +696,10 @@ export async function cmdSlackCreate(
|
|
|
681
696
|
ctx.err(
|
|
682
697
|
`The Slack connection is missing scopes provisioning needs; approving the consent link again grants them.\n`
|
|
683
698
|
);
|
|
699
|
+
} else if (error.code === "manager_reauth_required") {
|
|
700
|
+
ctx.err(
|
|
701
|
+
`Slack revoked Cursor's authorization for workspace ${workspace.slackTeamId}; re-approving the consent link restores it.\n`
|
|
702
|
+
);
|
|
684
703
|
} else {
|
|
685
704
|
ctx.err(
|
|
686
705
|
`The Slack workspace is not connected to Cursor; starting the connect flow.\n`
|
|
@@ -1119,8 +1138,7 @@ function renderCreateProvisionError(
|
|
|
1119
1138
|
);
|
|
1120
1139
|
return 1;
|
|
1121
1140
|
default:
|
|
1122
|
-
|
|
1123
|
-
return 1;
|
|
1141
|
+
return renderSlackApiError(error, err);
|
|
1124
1142
|
}
|
|
1125
1143
|
}
|
|
1126
1144
|
|
|
@@ -1128,11 +1146,17 @@ function renderSlackApiError(
|
|
|
1128
1146
|
error: unknown,
|
|
1129
1147
|
err: (text: string) => void
|
|
1130
1148
|
): number {
|
|
1131
|
-
if (error instanceof SlackProvisionApiError) {
|
|
1132
|
-
|
|
1133
|
-
return 1;
|
|
1149
|
+
if (!(error instanceof SlackProvisionApiError)) {
|
|
1150
|
+
throw error;
|
|
1134
1151
|
}
|
|
1135
|
-
|
|
1152
|
+
err(
|
|
1153
|
+
error.code === "manager_reauth_required"
|
|
1154
|
+
? // There is no standalone connect command: create runs the consent
|
|
1155
|
+
// flow before provisioning.
|
|
1156
|
+
`${error.message}\nRe-approve the Slack consent by re-running \`${CLI} slack create\`, which reconnects the workspace first.\n`
|
|
1157
|
+
: `${error.message}\n`
|
|
1158
|
+
);
|
|
1159
|
+
return 1;
|
|
1136
1160
|
}
|
|
1137
1161
|
|
|
1138
1162
|
async function fileExists(path: string): Promise<boolean> {
|
|
@@ -18,6 +18,29 @@ import {
|
|
|
18
18
|
|
|
19
19
|
export { RelayOffsetStore };
|
|
20
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Comment snapshot from scm-events compact metadata. Carries what the GitHub
|
|
23
|
+
* channel's `issue_comment` / `pull_request_review_comment` Zod schemas
|
|
24
|
+
* require so relayed comment wakes parse without re-fetching the comment.
|
|
25
|
+
*/
|
|
26
|
+
export type CursorScmCommentMeta = {
|
|
27
|
+
id: number;
|
|
28
|
+
body: string;
|
|
29
|
+
htmlUrl: string;
|
|
30
|
+
authorLogin?: string;
|
|
31
|
+
/**
|
|
32
|
+
* GitHub `user.type` ("User", "Bot", …). Carried onto the stubbed
|
|
33
|
+
* `comment.user` / `sender` so `isGitHubAppBotAuthor` keeps recognizing
|
|
34
|
+
* the agent's own comments on the relay path (self/bot-loop gate).
|
|
35
|
+
*/
|
|
36
|
+
authorType?: string;
|
|
37
|
+
/** Review comments only. */
|
|
38
|
+
path?: string;
|
|
39
|
+
line?: number;
|
|
40
|
+
pullRequestReviewId?: number;
|
|
41
|
+
inReplyToId?: number;
|
|
42
|
+
};
|
|
43
|
+
|
|
21
44
|
/** PR snapshot from scm-events compact metadata (Sand-style; no raw body). */
|
|
22
45
|
export type CursorScmPullRequestMeta = {
|
|
23
46
|
number: number;
|
|
@@ -53,6 +76,11 @@ export interface CursorScmEvent {
|
|
|
53
76
|
* stub a Zod-valid GitHub `pull_request` webhook body.
|
|
54
77
|
*/
|
|
55
78
|
pullRequest?: CursorScmPullRequestMeta;
|
|
79
|
+
/**
|
|
80
|
+
* Comment snapshot when the backend saw `body.comment`. Enough to stub a
|
|
81
|
+
* Zod-valid `issue_comment` / `pull_request_review_comment` webhook body.
|
|
82
|
+
*/
|
|
83
|
+
comment?: CursorScmCommentMeta;
|
|
56
84
|
/** GitHub-side event time from the payload, when present. */
|
|
57
85
|
occurredAtMs?: number;
|
|
58
86
|
}
|
|
@@ -114,6 +142,8 @@ export function formatCursorScmEventLog(event: {
|
|
|
114
142
|
* Minimal GitHub webhook body from an SCM-events metadata envelope.
|
|
115
143
|
* When `pullRequest` has title + head/base refs+shas, emits a Zod-valid
|
|
116
144
|
* `pull_request` object for `onPullRequest`; otherwise identity-only stubs.
|
|
145
|
+
* When `comment` is present, emits a Zod-valid `comment` object so relayed
|
|
146
|
+
* `issue_comment` / `pull_request_review_comment` deliveries dispatch.
|
|
117
147
|
*/
|
|
118
148
|
export function scmMetadataToGitHubPayload(event: {
|
|
119
149
|
repo: string;
|
|
@@ -121,6 +151,7 @@ export function scmMetadataToGitHubPayload(event: {
|
|
|
121
151
|
prNumber?: number;
|
|
122
152
|
prUrl?: string;
|
|
123
153
|
pullRequest?: CursorScmPullRequestMeta;
|
|
154
|
+
comment?: CursorScmCommentMeta;
|
|
124
155
|
}): Record<string, unknown> {
|
|
125
156
|
const slash = event.repo.indexOf("/");
|
|
126
157
|
const owner = slash === -1 ? "" : event.repo.slice(0, slash);
|
|
@@ -143,6 +174,42 @@ export function scmMetadataToGitHubPayload(event: {
|
|
|
143
174
|
};
|
|
144
175
|
payload.action = event.action;
|
|
145
176
|
|
|
177
|
+
const commentMeta = event.comment;
|
|
178
|
+
if (commentMeta !== undefined) {
|
|
179
|
+
const comment: Record<string, unknown> = {
|
|
180
|
+
id: commentMeta.id,
|
|
181
|
+
body: commentMeta.body,
|
|
182
|
+
html_url: commentMeta.htmlUrl,
|
|
183
|
+
};
|
|
184
|
+
if (commentMeta.authorLogin !== undefined) {
|
|
185
|
+
const author: Record<string, unknown> = {
|
|
186
|
+
login: commentMeta.authorLogin,
|
|
187
|
+
};
|
|
188
|
+
if (commentMeta.authorType !== undefined) {
|
|
189
|
+
// Without user.type the isGitHubAppBotAuthor gate fails open and the
|
|
190
|
+
// agent's own relayed comments could wake it again (bot loop).
|
|
191
|
+
author.type = commentMeta.authorType;
|
|
192
|
+
}
|
|
193
|
+
comment.user = author;
|
|
194
|
+
// The comment author is the closest thing the envelope has to the
|
|
195
|
+
// webhook sender; channels use it for self/bot-loop gates.
|
|
196
|
+
payload.sender = { ...author };
|
|
197
|
+
}
|
|
198
|
+
if (commentMeta.path !== undefined) {
|
|
199
|
+
comment.path = commentMeta.path;
|
|
200
|
+
}
|
|
201
|
+
if (commentMeta.line !== undefined) {
|
|
202
|
+
comment.line = commentMeta.line;
|
|
203
|
+
}
|
|
204
|
+
if (commentMeta.pullRequestReviewId !== undefined) {
|
|
205
|
+
comment.pull_request_review_id = commentMeta.pullRequestReviewId;
|
|
206
|
+
}
|
|
207
|
+
if (commentMeta.inReplyToId !== undefined) {
|
|
208
|
+
comment.in_reply_to_id = commentMeta.inReplyToId;
|
|
209
|
+
}
|
|
210
|
+
payload.comment = comment;
|
|
211
|
+
}
|
|
212
|
+
|
|
146
213
|
const title = prMeta?.title;
|
|
147
214
|
const headRef = prMeta?.headRef;
|
|
148
215
|
const headSha = prMeta?.headSha;
|
|
@@ -26,6 +26,8 @@ import {
|
|
|
26
26
|
type AgentHostingOptions,
|
|
27
27
|
type AgentProject,
|
|
28
28
|
type AgentProjectInfo,
|
|
29
|
+
type AgentRuntime,
|
|
30
|
+
type BuiltinToolsConfig,
|
|
29
31
|
type ChannelDefinition,
|
|
30
32
|
type ConnectionDefinition,
|
|
31
33
|
type ConnectionTransport,
|
|
@@ -56,6 +58,7 @@ import {
|
|
|
56
58
|
import { registerAuthoredModuleLoaders } from "./authored-loaders.js";
|
|
57
59
|
import { normalizePositiveInt } from "./bounded-int.js";
|
|
58
60
|
import { getDefinitionKind } from "./brand.js";
|
|
61
|
+
import { builtinTools } from "./builtin-tools/index.js";
|
|
59
62
|
import { isValidCron } from "./cron.js";
|
|
60
63
|
import { parseFrontmatter } from "./frontmatter.js";
|
|
61
64
|
import {
|
|
@@ -341,6 +344,13 @@ async function loadAgent(
|
|
|
341
344
|
// warn that they exist.
|
|
342
345
|
if (!options.isRoot) {
|
|
343
346
|
await warnUnsupportedSubagentSlots(ctx, agentDir);
|
|
347
|
+
if (config?.builtinTools !== undefined) {
|
|
348
|
+
ctx.diagnostics.push({
|
|
349
|
+
severity: "warning",
|
|
350
|
+
path: displayPath(ctx, join(agentDir, "agent.ts")),
|
|
351
|
+
message: "builtinTools is not supported on subagents; ignoring.",
|
|
352
|
+
});
|
|
353
|
+
}
|
|
344
354
|
return {
|
|
345
355
|
name,
|
|
346
356
|
...(config?.description === undefined
|
|
@@ -401,6 +411,8 @@ async function loadAgent(
|
|
|
401
411
|
});
|
|
402
412
|
}
|
|
403
413
|
|
|
414
|
+
appendBuiltinTools(ctx, agentDir, tools, config?.builtinTools, runtime);
|
|
415
|
+
|
|
404
416
|
return {
|
|
405
417
|
name,
|
|
406
418
|
description: config?.description,
|
|
@@ -1161,6 +1173,46 @@ async function warnUnsupportedSubagentSlots(
|
|
|
1161
1173
|
}
|
|
1162
1174
|
}
|
|
1163
1175
|
|
|
1176
|
+
/**
|
|
1177
|
+
* Append enabled built-in tools after authored tools. Authored tools with
|
|
1178
|
+
* the same name win; built-ins are server tools, so cloud runtimes get a
|
|
1179
|
+
* warning instead of tools.
|
|
1180
|
+
*/
|
|
1181
|
+
function appendBuiltinTools(
|
|
1182
|
+
ctx: DiscoveryContext,
|
|
1183
|
+
agentDir: string,
|
|
1184
|
+
tools: DiscoveredTool[],
|
|
1185
|
+
config: BuiltinToolsConfig | undefined,
|
|
1186
|
+
runtime: AgentRuntime
|
|
1187
|
+
): void {
|
|
1188
|
+
const builtin = builtinTools(config);
|
|
1189
|
+
if (builtin.length === 0) {
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const agentPath = displayPath(ctx, join(agentDir, "agent.ts"));
|
|
1193
|
+
if (runtime === "cloud") {
|
|
1194
|
+
ctx.diagnostics.push({
|
|
1195
|
+
severity: "warning",
|
|
1196
|
+
path: agentPath,
|
|
1197
|
+
message:
|
|
1198
|
+
"builtinTools are in-process server tools and are not available on cloud agents; they are skipped.",
|
|
1199
|
+
});
|
|
1200
|
+
return;
|
|
1201
|
+
}
|
|
1202
|
+
const authored = new Set(tools.map((tool) => tool.name));
|
|
1203
|
+
for (const tool of builtin) {
|
|
1204
|
+
if (authored.has(tool.name)) {
|
|
1205
|
+
ctx.diagnostics.push({
|
|
1206
|
+
severity: "warning",
|
|
1207
|
+
path: agentPath,
|
|
1208
|
+
message: `Authored tool "${tool.name}" shadows the built-in tool of the same name; the authored tool wins.`,
|
|
1209
|
+
});
|
|
1210
|
+
continue;
|
|
1211
|
+
}
|
|
1212
|
+
tools.push(tool);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1164
1216
|
function warnCloudRuntimeLimitations(
|
|
1165
1217
|
ctx: DiscoveryContext,
|
|
1166
1218
|
agentDir: string,
|
|
@@ -336,6 +336,36 @@ export class ReminderRunner {
|
|
|
336
336
|
return n;
|
|
337
337
|
}
|
|
338
338
|
|
|
339
|
+
/**
|
|
340
|
+
* Re-key armed reminders when a channel rotates a session's continuation
|
|
341
|
+
* token (the built-in HTTP channel rotates after every follow-up). Without
|
|
342
|
+
* this, a reminder armed before the rotation misses its session at fire
|
|
343
|
+
* time and disarms as `session_gone`.
|
|
344
|
+
*/
|
|
345
|
+
async rekeyContinuation(
|
|
346
|
+
channelId: string,
|
|
347
|
+
oldToken: string,
|
|
348
|
+
newToken: string
|
|
349
|
+
): Promise<number> {
|
|
350
|
+
const armed = await this.store.listArmed();
|
|
351
|
+
let n = 0;
|
|
352
|
+
for (const record of armed) {
|
|
353
|
+
if (
|
|
354
|
+
record.channelId !== channelId ||
|
|
355
|
+
record.continuationToken !== oldToken
|
|
356
|
+
) {
|
|
357
|
+
continue;
|
|
358
|
+
}
|
|
359
|
+
await this.persist({
|
|
360
|
+
...record,
|
|
361
|
+
continuationToken: newToken,
|
|
362
|
+
updatedAt: new Date().toISOString(),
|
|
363
|
+
});
|
|
364
|
+
n += 1;
|
|
365
|
+
}
|
|
366
|
+
return n;
|
|
367
|
+
}
|
|
368
|
+
|
|
339
369
|
/** Fire one reminder immediately (dev / hillclimb). */
|
|
340
370
|
async dispatch(reminderId: string): Promise<{
|
|
341
371
|
reminderId: string;
|