@cursor/july 0.1.15 → 0.1.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -4
- package/dist/bin/agent-serve.js +60 -2
- package/dist/channels/slack/api.d.ts.map +1 -1
- package/dist/channels/slack/api.js +13 -3
- package/dist/channels/slack/auth.d.ts +7 -1
- package/dist/channels/slack/auth.d.ts.map +1 -1
- package/dist/channels/slack/auth.js +14 -0
- package/dist/channels/slack/blocks.d.ts +61 -0
- package/dist/channels/slack/blocks.d.ts.map +1 -0
- package/dist/channels/slack/blocks.js +90 -0
- package/dist/channels/slack/defaults.d.ts +2 -0
- package/dist/channels/slack/defaults.d.ts.map +1 -1
- package/dist/channels/slack/defaults.js +29 -3
- package/dist/channels/slack/dispatch.d.ts +3 -17
- package/dist/channels/slack/dispatch.d.ts.map +1 -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/interactive.d.ts +39 -3
- package/dist/channels/slack/interactive.d.ts.map +1 -1
- package/dist/channels/slack/interactive.js +152 -7
- package/dist/channels/slack/manifest.d.ts +3 -0
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +3 -0
- package/dist/channels/slack/setup.d.ts +4 -1
- package/dist/channels/slack/setup.d.ts.map +1 -1
- package/dist/channels/slack/setup.js +16 -2
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +24 -3
- package/dist/channels/slack/types.d.ts +77 -0
- package/dist/channels/slack/types.d.ts.map +1 -1
- package/dist/connections.d.ts +4 -1
- package/dist/connections.d.ts.map +1 -1
- package/dist/connections.js +4 -1
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.DpdGZTLe.js → app.BbEXr2KO.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DCBsrfwP.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.IB-0QqIT.js → VPLocalSearchBox.DBEi4HDp.js} +1 -1
- package/dist/docs/assets/chunks/{theme.-fYBoNtR.js → theme.DGfptcHw.js} +2 -2
- package/dist/docs/assets/{example-agents_index.md.BPXjUtRB.js → example-agents_index.md.D2PEVSXl.js} +2 -2
- package/dist/docs/assets/{example-agents_index.md.BPXjUtRB.lean.js → example-agents_index.md.D2PEVSXl.lean.js} +1 -1
- package/dist/docs/assets/example-agents_security-reviewer.md.BZSNycMf.js +19 -0
- package/dist/docs/assets/{example-agents_security-reviewer.md.CE9v-aKm.lean.js → example-agents_security-reviewer.md.BZSNycMf.lean.js} +1 -1
- package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.js → guides_mcp-oauth.md.Dd8EgSem.js} +3 -2
- package/dist/docs/assets/{guides_mcp-oauth.md.DqmEOQY1.lean.js → guides_mcp-oauth.md.Dd8EgSem.lean.js} +1 -1
- package/dist/docs/assets/{guides_slack.md.FWale6Ip.js → guides_slack.md.D_xFKlnF.js} +1 -1
- package/dist/docs/assets/{reference_connections.md.CsSr3rDz.js → reference_connections.md.B9Q3TOve.js} +12 -5
- package/dist/docs/assets/{reference_connections.md.CsSr3rDz.lean.js → reference_connections.md.B9Q3TOve.lean.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 +5 -5
- 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 +14 -14
- 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 +6 -5
- package/dist/docs/guides/slack.html +5 -5
- 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 +14 -7
- 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/evals/assertions.d.ts +190 -0
- package/dist/evals/assertions.d.ts.map +1 -0
- package/dist/evals/assertions.js +461 -0
- package/dist/evals/expect.d.ts +95 -0
- package/dist/evals/expect.d.ts.map +1 -0
- package/dist/evals/expect.js +194 -0
- package/dist/evals/judge.d.ts +133 -0
- package/dist/evals/judge.d.ts.map +1 -0
- package/dist/evals/judge.js +228 -0
- package/dist/evals/loaders.d.ts +51 -0
- package/dist/evals/loaders.d.ts.map +1 -0
- package/dist/evals/loaders.js +124 -0
- package/dist/evals/matchers.d.ts +95 -0
- package/dist/evals/matchers.d.ts.map +1 -0
- package/dist/evals/matchers.js +145 -0
- package/dist/evals/reporters.d.ts +70 -0
- package/dist/evals/reporters.d.ts.map +1 -0
- package/dist/evals/reporters.js +221 -0
- package/dist/evals/results.d.ts +103 -0
- package/dist/evals/results.d.ts.map +1 -0
- package/dist/evals/results.js +28 -0
- package/dist/evals/run-facts.d.ts +77 -0
- package/dist/evals/run-facts.d.ts.map +1 -0
- package/dist/evals/run-facts.js +187 -0
- package/dist/evals.d.ts +136 -31
- package/dist/evals.d.ts.map +1 -1
- package/dist/evals.js +76 -12
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/cli-ax.d.ts +27 -0
- package/dist/internal/cli-ax.d.ts.map +1 -1
- package/dist/internal/cli-ax.js +90 -12
- package/dist/internal/cli-mcp.d.ts +8 -0
- package/dist/internal/cli-mcp.d.ts.map +1 -1
- package/dist/internal/cli-mcp.js +98 -0
- package/dist/internal/cli-slack.d.ts +29 -1
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +777 -12
- package/dist/internal/deploy-client.d.ts +7 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +9 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +29 -9
- package/dist/internal/distribution.d.ts.map +1 -1
- package/dist/internal/distribution.js +2 -0
- package/dist/internal/eval-judge-model.d.ts +59 -0
- package/dist/internal/eval-judge-model.d.ts.map +1 -0
- package/dist/internal/eval-judge-model.js +131 -0
- package/dist/internal/eval-run-store.d.ts.map +1 -1
- package/dist/internal/eval-run-store.js +12 -20
- package/dist/internal/eval-runner.d.ts +31 -25
- package/dist/internal/eval-runner.d.ts.map +1 -1
- package/dist/internal/eval-runner.js +332 -128
- package/dist/internal/evals-client.d.ts.map +1 -1
- package/dist/internal/evals-client.js +7 -3
- package/dist/internal/local-env.d.ts +8 -0
- package/dist/internal/local-env.d.ts.map +1 -0
- package/dist/internal/local-env.js +47 -0
- package/dist/internal/playground/static.d.ts +3 -0
- package/dist/internal/playground/static.d.ts.map +1 -1
- package/dist/internal/playground/static.js +3 -0
- package/dist/internal/run-client.d.ts +15 -0
- package/dist/internal/run-client.d.ts.map +1 -1
- package/dist/internal/run-client.js +3 -2
- package/dist/internal/sdk-runner.d.ts +36 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +65 -25
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +10 -1
- package/dist/internal/session-engine.d.ts +18 -5
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +72 -19
- package/dist/internal/slack-provision-client.d.ts +130 -0
- package/dist/internal/slack-provision-client.d.ts.map +1 -0
- package/dist/internal/slack-provision-client.js +284 -0
- package/dist/playground/assets/cursor-icons-16-f_W_ogc-.woff2 +0 -0
- package/dist/playground/assets/index-CidizGZv.css +1 -0
- package/dist/playground/assets/index-DNqirsrK.js +85 -0
- package/dist/playground/index.html +2 -2
- package/dist/types.d.ts +70 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +26 -0
- package/docs/example-agents/index.md +1 -1
- package/docs/example-agents/security-reviewer.md +23 -21
- package/docs/guides/mcp-oauth.md +10 -5
- package/docs/guides/slack.md +3 -2
- package/docs/reference/connections.md +28 -18
- package/package.json +20 -2
- package/skills/create-agent/SKILL.md +15 -7
- package/skills/debug/SKILL.md +3 -1
- package/skills/mcp-auth/SKILL.md +1 -1
- package/skills/setup-slack/SKILL.md +173 -11
- package/src/bin/agent-serve.ts +63 -2
- package/src/channels/slack/api.ts +16 -3
- package/src/channels/slack/auth.ts +16 -1
- package/src/channels/slack/blocks.ts +139 -0
- package/src/channels/slack/defaults.ts +32 -4
- package/src/channels/slack/dispatch.ts +3 -18
- package/src/channels/slack/index.ts +1 -0
- package/src/channels/slack/interactive.ts +183 -19
- package/src/channels/slack/manifest.ts +3 -0
- package/src/channels/slack/setup.ts +16 -2
- package/src/channels/slack/slack-channel.ts +37 -6
- package/src/channels/slack/types.ts +89 -0
- package/src/connections.ts +4 -1
- package/src/evals/assertions.ts +704 -0
- package/src/evals/expect.ts +272 -0
- package/src/evals/judge.ts +346 -0
- package/src/evals/loaders.ts +113 -0
- package/src/evals/matchers.ts +246 -0
- package/src/evals/reporters.ts +274 -0
- package/src/evals/results.ts +132 -0
- package/src/evals/run-facts.ts +274 -0
- package/src/evals.ts +249 -42
- package/src/index.ts +2 -0
- package/src/internal/cli-ax.ts +134 -15
- package/src/internal/cli-mcp.ts +118 -5
- package/src/internal/cli-slack.ts +993 -15
- package/src/internal/deploy-client.ts +10 -0
- package/src/internal/discovery.ts +31 -5
- package/src/internal/distribution.ts +2 -0
- package/src/internal/eval-judge-model.ts +148 -0
- package/src/internal/eval-run-store.ts +10 -14
- package/src/internal/eval-runner.ts +466 -215
- package/src/internal/evals-client.ts +14 -2
- package/src/internal/local-env.ts +53 -0
- package/src/internal/playground/static.ts +3 -0
- package/src/internal/run-client.ts +17 -1
- package/src/internal/sdk-runner.ts +103 -22
- package/src/internal/server.ts +10 -0
- package/src/internal/session-engine.ts +89 -21
- package/src/internal/slack-provision-client.ts +438 -0
- package/src/types.ts +92 -4
- package/dist/channels/slack/post-update-delivery.d.ts +0 -85
- package/dist/channels/slack/post-update-delivery.d.ts.map +0 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.D2d8CfFS.js +0 -1
- package/dist/docs/assets/example-agents_security-reviewer.md.CE9v-aKm.js +0 -19
- package/dist/internal/json-dir-store.d.ts +0 -32
- package/dist/internal/json-dir-store.d.ts.map +0 -1
- package/dist/internal/persistence-coordinator.d.ts +0 -127
- package/dist/internal/persistence-coordinator.d.ts.map +0 -1
- package/dist/persistence.d.ts +0 -184
- package/dist/persistence.d.ts.map +0 -1
- package/dist/playground/assets/cursor-icons-16-CQ50JpfO.woff2 +0 -0
- package/dist/playground/assets/index-BVMOoiek.js +0 -86
- package/dist/playground/assets/index-DYmP4b--.css +0 -1
- /package/dist/docs/assets/{guides_slack.md.FWale6Ip.lean.js → guides_slack.md.D_xFKlnF.lean.js} +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Block Kit building helpers shared by card-building agents.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/** Slack cap for one section block's mrkdwn text. */
|
|
6
|
+
export const SLACK_SECTION_MRKDWN_MAX = 3000;
|
|
7
|
+
|
|
8
|
+
/** Slack caps for a native `table` block (one table per message). */
|
|
9
|
+
export const SLACK_TABLE_MAX_ROWS = 100;
|
|
10
|
+
export const SLACK_TABLE_MAX_COLUMNS = 20;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Pack lines into as few mrkdwn section blocks as fit the 3000-char cap,
|
|
14
|
+
* preserving order. Long lists (a full service fleet, a file inventory)
|
|
15
|
+
* overflow a single section and make `chat.postMessage` refuse the whole
|
|
16
|
+
* card with `invalid_blocks`.
|
|
17
|
+
*/
|
|
18
|
+
export function mrkdwnSections(lines: readonly string[]): unknown[] {
|
|
19
|
+
const texts: string[] = [];
|
|
20
|
+
let current = "";
|
|
21
|
+
for (const raw of lines) {
|
|
22
|
+
// A single oversized line (one huge blob) hard-splits at the cap.
|
|
23
|
+
const parts: string[] = [];
|
|
24
|
+
for (
|
|
25
|
+
let i = 0;
|
|
26
|
+
i < Math.max(raw.length, 1);
|
|
27
|
+
i += SLACK_SECTION_MRKDWN_MAX
|
|
28
|
+
) {
|
|
29
|
+
parts.push(raw.slice(i, i + SLACK_SECTION_MRKDWN_MAX));
|
|
30
|
+
}
|
|
31
|
+
for (const line of parts) {
|
|
32
|
+
const next = current === "" ? line : `${current}\n${line}`;
|
|
33
|
+
if (next.length > SLACK_SECTION_MRKDWN_MAX && current !== "") {
|
|
34
|
+
texts.push(current);
|
|
35
|
+
current = line;
|
|
36
|
+
} else {
|
|
37
|
+
current = next;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
if (current !== "") {
|
|
42
|
+
texts.push(current);
|
|
43
|
+
}
|
|
44
|
+
return texts.map((text) => ({
|
|
45
|
+
type: "section",
|
|
46
|
+
text: { type: "mrkdwn", text },
|
|
47
|
+
}));
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export type SlackTableAlign = "left" | "center" | "right";
|
|
51
|
+
|
|
52
|
+
export type SlackTableColumnSetting = {
|
|
53
|
+
align?: SlackTableAlign;
|
|
54
|
+
is_wrapped?: boolean;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** Cell types accepted by Slack's native `table` block. */
|
|
58
|
+
export type SlackTableCell =
|
|
59
|
+
| { type: "raw_text"; text: string }
|
|
60
|
+
| {
|
|
61
|
+
type: "rich_text";
|
|
62
|
+
elements: Array<{
|
|
63
|
+
type: "rich_text_section";
|
|
64
|
+
elements: Array<
|
|
65
|
+
| { type: "text"; text: string }
|
|
66
|
+
| { type: "link"; url: string; text: string }
|
|
67
|
+
>;
|
|
68
|
+
}>;
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export type SlackTableBlock = {
|
|
72
|
+
type: "table";
|
|
73
|
+
rows: SlackTableCell[][];
|
|
74
|
+
column_settings?: SlackTableColumnSetting[];
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
/** Plain text cell (header or body). */
|
|
78
|
+
export function slackRawTextCell(text: string): SlackTableCell {
|
|
79
|
+
return { type: "raw_text", text };
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Linked cell via rich_text (preferred over mrkdwn `<url|label>` in tables). */
|
|
83
|
+
export function slackLinkCell(args: {
|
|
84
|
+
url: string;
|
|
85
|
+
text: string;
|
|
86
|
+
}): SlackTableCell {
|
|
87
|
+
return {
|
|
88
|
+
type: "rich_text",
|
|
89
|
+
elements: [
|
|
90
|
+
{
|
|
91
|
+
type: "rich_text_section",
|
|
92
|
+
elements: [{ type: "link", url: args.url, text: args.text }],
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Build a native Slack `table` block. First row is the header. Slack allows
|
|
100
|
+
* at most one table block per message, 100 rows, and 20 columns — callers
|
|
101
|
+
* should keep tables short.
|
|
102
|
+
*/
|
|
103
|
+
export function slackTableBlock(args: {
|
|
104
|
+
headers: readonly string[];
|
|
105
|
+
rows: readonly (readonly SlackTableCell[])[];
|
|
106
|
+
columnSettings?: readonly SlackTableColumnSetting[];
|
|
107
|
+
}): SlackTableBlock | undefined {
|
|
108
|
+
if (args.headers.length === 0) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
if (args.headers.length > SLACK_TABLE_MAX_COLUMNS) {
|
|
112
|
+
throw new Error(
|
|
113
|
+
`slackTableBlock: ${args.headers.length} columns exceeds Slack cap of ${SLACK_TABLE_MAX_COLUMNS}`
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
// Header + body rows share the 100-row cap.
|
|
117
|
+
if (args.rows.length + 1 > SLACK_TABLE_MAX_ROWS) {
|
|
118
|
+
throw new Error(
|
|
119
|
+
`slackTableBlock: ${args.rows.length + 1} rows exceeds Slack cap of ${SLACK_TABLE_MAX_ROWS}`
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
const columnCount = args.headers.length;
|
|
123
|
+
for (const [index, row] of args.rows.entries()) {
|
|
124
|
+
if (row.length !== columnCount) {
|
|
125
|
+
throw new Error(
|
|
126
|
+
`slackTableBlock: row ${index} has ${row.length} cells; expected ${columnCount}`
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const headerRow = args.headers.map((header) => slackRawTextCell(header));
|
|
131
|
+
const block: SlackTableBlock = {
|
|
132
|
+
type: "table",
|
|
133
|
+
rows: [headerRow, ...args.rows.map((row) => [...row])],
|
|
134
|
+
};
|
|
135
|
+
if (args.columnSettings !== undefined && args.columnSettings.length > 0) {
|
|
136
|
+
block.column_settings = [...args.columnSettings];
|
|
137
|
+
}
|
|
138
|
+
return block;
|
|
139
|
+
}
|
|
@@ -26,6 +26,37 @@ import type {
|
|
|
26
26
|
SlackSuggestedPrompt,
|
|
27
27
|
} from "./types.js";
|
|
28
28
|
|
|
29
|
+
const SLACK_TURN_FAILURE_FALLBACK =
|
|
30
|
+
"I hit an error while handling your request. Please try again or rephrase.";
|
|
31
|
+
const SLACK_TURN_FAILURE_MAX_CHARS = 400;
|
|
32
|
+
|
|
33
|
+
/** Scrubbed turn.failed body for Slack (secrets / paths / URLs stripped). */
|
|
34
|
+
export function formatSlackTurnFailureMessage(raw: string | undefined): string {
|
|
35
|
+
const scrubbed = scrubSlackErrorDetail(raw?.trim() ?? "");
|
|
36
|
+
if (scrubbed === "") {
|
|
37
|
+
return SLACK_TURN_FAILURE_FALLBACK;
|
|
38
|
+
}
|
|
39
|
+
if (/^I hit an error/i.test(scrubbed)) {
|
|
40
|
+
return scrubbed;
|
|
41
|
+
}
|
|
42
|
+
return `I hit an error while handling your request: ${scrubbed}`;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function scrubSlackErrorDetail(message: string): string {
|
|
46
|
+
const scrubbed = message
|
|
47
|
+
.replace(/https?:\/\/\S+/gi, "[url]")
|
|
48
|
+
.replace(/\b(crsr_|xox[baprs]-|sk-|Bearer\s+)\S+/gi, "[redacted]")
|
|
49
|
+
.replace(/\/(?:home|Users|var\/folders)\/\S+/g, "[path]")
|
|
50
|
+
.replace(/\b[A-Za-z]:(?:\\|\/)[^\s]+/g, "[path]")
|
|
51
|
+
.replace(/\\\\[^\s]+/g, "[path]")
|
|
52
|
+
.replace(/\s+/g, " ")
|
|
53
|
+
.trim();
|
|
54
|
+
if (scrubbed.length <= SLACK_TURN_FAILURE_MAX_CHARS) {
|
|
55
|
+
return scrubbed;
|
|
56
|
+
}
|
|
57
|
+
return `${scrubbed.slice(0, SLACK_TURN_FAILURE_MAX_CHARS - 1)}…`;
|
|
58
|
+
}
|
|
59
|
+
|
|
29
60
|
function resolveLoadingMessages(
|
|
30
61
|
loadingMessages: readonly string[] | undefined
|
|
31
62
|
): string[] {
|
|
@@ -434,10 +465,7 @@ export function buildDefaultEvents(
|
|
|
434
465
|
if (state.channelId === null || state.threadTs === null) {
|
|
435
466
|
return;
|
|
436
467
|
}
|
|
437
|
-
|
|
438
|
-
// paths, tokens, or other sensitive detail to workspace members.
|
|
439
|
-
const errorText =
|
|
440
|
-
"I hit an error while handling your request. Please try again or rephrase.";
|
|
468
|
+
const errorText = formatSlackTurnFailureMessage(event.data.message);
|
|
441
469
|
|
|
442
470
|
let delivered = false;
|
|
443
471
|
if (state.streamTs !== null && state.streamTs !== "") {
|
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
} from "./thread-context.js";
|
|
26
26
|
import type {
|
|
27
27
|
SlackChannelState,
|
|
28
|
+
SlackContinuationProbes,
|
|
28
29
|
SlackCredentials,
|
|
29
30
|
SlackDispatchDecision,
|
|
30
31
|
SlackMessage,
|
|
@@ -55,7 +56,7 @@ export function emptySlackChannelState(
|
|
|
55
56
|
};
|
|
56
57
|
}
|
|
57
58
|
|
|
58
|
-
export interface SlackInboundDispatchDeps {
|
|
59
|
+
export interface SlackInboundDispatchDeps extends SlackContinuationProbes {
|
|
59
60
|
send: SendMessageFn;
|
|
60
61
|
waitUntil: (promise: Promise<unknown>) => void;
|
|
61
62
|
onAppMention: SlackMessageHandler;
|
|
@@ -70,22 +71,6 @@ export interface SlackInboundDispatchDeps {
|
|
|
70
71
|
directMessagesEnabled?: boolean;
|
|
71
72
|
/** Opt-in channel watch; absent = channel posts are ignored. */
|
|
72
73
|
channelWatch?: ChannelPostWatcher;
|
|
73
|
-
/**
|
|
74
|
-
* When set, full-thread hydration runs only for cold-start turns (no
|
|
75
|
-
* existing session). Warm turns load a `<slack_thread_delta>` instead.
|
|
76
|
-
*/
|
|
77
|
-
hasContinuationSession?: (continuationToken: string) => Promise<boolean>;
|
|
78
|
-
/** True when a turn is already running for this Slack continuation. */
|
|
79
|
-
isContinuationBusy?: (continuationToken: string) => Promise<boolean>;
|
|
80
|
-
/**
|
|
81
|
-
* Warm-delta watermark from session channel state (`lastBotMessageTs`).
|
|
82
|
-
* When absent, delta falls back to the last matching bot post in-thread.
|
|
83
|
-
*/
|
|
84
|
-
getContinuationLastBotMessageTs?: (
|
|
85
|
-
continuationToken: string
|
|
86
|
-
) => Promise<string | undefined>;
|
|
87
|
-
/** Cancel the active turn + clear coalesced nudges for this continuation. */
|
|
88
|
-
interruptContinuation?: (continuationToken: string) => Promise<boolean>;
|
|
89
74
|
/**
|
|
90
75
|
* When false, skip the built-in `eval` directive. Defaults to true when
|
|
91
76
|
* `host.evals` is present.
|
|
@@ -111,7 +96,7 @@ function triggeringMergeMessage(
|
|
|
111
96
|
}
|
|
112
97
|
|
|
113
98
|
export async function dispatchInboundMessage(args: {
|
|
114
|
-
kind: "app_mention" | "direct_message" | "channel_post";
|
|
99
|
+
kind: "app_mention" | "direct_message" | "channel_post" | "block_action";
|
|
115
100
|
message: SlackMessage;
|
|
116
101
|
handler: SlackMessageHandler;
|
|
117
102
|
send: SendMessageFn;
|
|
@@ -22,6 +22,7 @@ export * from "./api.js";
|
|
|
22
22
|
export * from "./approvals.js";
|
|
23
23
|
export * from "./assistant.js";
|
|
24
24
|
export * from "./auth.js";
|
|
25
|
+
export * from "./blocks.js";
|
|
25
26
|
export * from "./channel-watch.js";
|
|
26
27
|
export * from "./constants.js";
|
|
27
28
|
export * from "./credentials.js";
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Socket Mode interactive payloads (Block Kit
|
|
2
|
+
* Socket Mode interactive payloads (Block Kit clicks).
|
|
3
|
+
*
|
|
4
|
+
* Built-in tool-approval Approve/Deny actions resolve parked calls
|
|
5
|
+
* ({@link dispatchInteractiveFromPayload}); everything else can be routed
|
|
6
|
+
* to an author handler ({@link dispatchBlockAction} via
|
|
7
|
+
* `slackChannel({ onBlockAction })`).
|
|
3
8
|
*/
|
|
4
9
|
|
|
5
10
|
import type {
|
|
6
11
|
ApprovalDecision,
|
|
7
12
|
AuthContext,
|
|
8
13
|
ChannelSession,
|
|
14
|
+
HostContext,
|
|
9
15
|
PendingApproval,
|
|
16
|
+
SendMessageFn,
|
|
10
17
|
} from "../../types.js";
|
|
18
|
+
import { updateMessage } from "./api.js";
|
|
11
19
|
import { decisionFromActionId, parseApprovalActionValue } from "./approvals.js";
|
|
12
20
|
import { buildSlackInteractiveApprovalAuth } from "./auth.js";
|
|
21
|
+
import { dispatchInboundMessage } from "./dispatch.js";
|
|
13
22
|
import { defaultSlackLog, type SlackLog } from "./log.js";
|
|
14
|
-
import type {
|
|
23
|
+
import type {
|
|
24
|
+
SlackBlockAction,
|
|
25
|
+
SlackBlockActionHandler,
|
|
26
|
+
SlackBotToken,
|
|
27
|
+
SlackContinuationProbes,
|
|
28
|
+
SlackCredentials,
|
|
29
|
+
SlackMessage,
|
|
30
|
+
} from "./types.js";
|
|
15
31
|
|
|
16
32
|
export interface SlackInteractiveDispatchDeps {
|
|
17
33
|
credentials: SlackCredentials | undefined;
|
|
@@ -35,7 +51,7 @@ export async function dispatchInteractiveFromPayload(
|
|
|
35
51
|
deps: SlackInteractiveDispatchDeps
|
|
36
52
|
): Promise<boolean> {
|
|
37
53
|
const log = deps.log ?? defaultSlackLog;
|
|
38
|
-
const parsed =
|
|
54
|
+
const parsed = parseSlackBlockAction(payload);
|
|
39
55
|
if (parsed === null) {
|
|
40
56
|
return false;
|
|
41
57
|
}
|
|
@@ -124,16 +140,14 @@ export async function dispatchInteractiveFromPayload(
|
|
|
124
140
|
return true;
|
|
125
141
|
}
|
|
126
142
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
function parseBlockActionsPayload(payload: unknown): ParsedBlockAction | null {
|
|
143
|
+
/**
|
|
144
|
+
* Parse a Socket Mode `block_actions` payload into a {@link SlackBlockAction}.
|
|
145
|
+
* Returns null for non-`block_actions` payloads (view submissions, shortcuts)
|
|
146
|
+
* and malformed bodies.
|
|
147
|
+
*/
|
|
148
|
+
export function parseSlackBlockAction(
|
|
149
|
+
payload: unknown
|
|
150
|
+
): SlackBlockAction | null {
|
|
137
151
|
if (typeof payload !== "object" || payload === null) {
|
|
138
152
|
return null;
|
|
139
153
|
}
|
|
@@ -150,10 +164,7 @@ function parseBlockActionsPayload(payload: unknown): ParsedBlockAction | null {
|
|
|
150
164
|
return null;
|
|
151
165
|
}
|
|
152
166
|
const action = first as Record<string, unknown>;
|
|
153
|
-
if (
|
|
154
|
-
typeof action.action_id !== "string" ||
|
|
155
|
-
typeof action.value !== "string"
|
|
156
|
-
) {
|
|
167
|
+
if (typeof action.action_id !== "string") {
|
|
157
168
|
return null;
|
|
158
169
|
}
|
|
159
170
|
|
|
@@ -169,6 +180,10 @@ function parseBlockActionsPayload(payload: unknown): ParsedBlockAction | null {
|
|
|
169
180
|
typeof record.user === "object" && record.user !== null
|
|
170
181
|
? (record.user as Record<string, unknown>)
|
|
171
182
|
: {};
|
|
183
|
+
const team =
|
|
184
|
+
typeof record.team === "object" && record.team !== null
|
|
185
|
+
? (record.team as Record<string, unknown>)
|
|
186
|
+
: {};
|
|
172
187
|
|
|
173
188
|
const channelId = typeof channel.id === "string" ? channel.id : "";
|
|
174
189
|
const messageTs = typeof message.ts === "string" ? message.ts : "";
|
|
@@ -182,13 +197,162 @@ function parseBlockActionsPayload(payload: unknown): ParsedBlockAction | null {
|
|
|
182
197
|
? (record.container as { thread_ts: string }).thread_ts
|
|
183
198
|
: messageTs;
|
|
184
199
|
const userId = typeof user.id === "string" ? user.id : "";
|
|
200
|
+
const userName =
|
|
201
|
+
typeof user.username === "string"
|
|
202
|
+
? user.username
|
|
203
|
+
: typeof user.name === "string"
|
|
204
|
+
? user.name
|
|
205
|
+
: undefined;
|
|
206
|
+
const teamId =
|
|
207
|
+
typeof team.id === "string"
|
|
208
|
+
? team.id
|
|
209
|
+
: typeof user.team_id === "string"
|
|
210
|
+
? user.team_id
|
|
211
|
+
: undefined;
|
|
212
|
+
const label =
|
|
213
|
+
typeof action.text === "object" &&
|
|
214
|
+
action.text !== null &&
|
|
215
|
+
typeof (action.text as { text?: unknown }).text === "string"
|
|
216
|
+
? (action.text as { text: string }).text
|
|
217
|
+
: undefined;
|
|
218
|
+
const actionTs = typeof action.action_ts === "string" ? action.action_ts : "";
|
|
219
|
+
const messageBlocks = Array.isArray(message.blocks)
|
|
220
|
+
? message.blocks
|
|
221
|
+
: undefined;
|
|
185
222
|
|
|
186
223
|
return {
|
|
187
224
|
actionId: action.action_id,
|
|
188
|
-
|
|
225
|
+
blockId: typeof action.block_id === "string" ? action.block_id : "",
|
|
226
|
+
value: typeof action.value === "string" ? action.value : "",
|
|
227
|
+
label,
|
|
189
228
|
channelId,
|
|
190
229
|
threadTs,
|
|
191
|
-
userId,
|
|
192
230
|
messageTs,
|
|
231
|
+
messageBlocks,
|
|
232
|
+
userId,
|
|
233
|
+
userName,
|
|
234
|
+
teamId,
|
|
235
|
+
actionTs,
|
|
193
236
|
};
|
|
194
237
|
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Swap the clicked message's actions row for a context note (`chat.update`).
|
|
241
|
+
* Strips only the row the click came from (`action.blockId`); every other
|
|
242
|
+
* block survives. Soft: false when the payload carried no message blocks or
|
|
243
|
+
* the update failed — callers treat the dispatched turn as the durable
|
|
244
|
+
* record and the card as best-effort UI.
|
|
245
|
+
*/
|
|
246
|
+
export async function resolveBlockActionCard(args: {
|
|
247
|
+
botToken: SlackBotToken | undefined;
|
|
248
|
+
action: SlackBlockAction;
|
|
249
|
+
note: string;
|
|
250
|
+
}): Promise<boolean> {
|
|
251
|
+
const { action, note } = args;
|
|
252
|
+
if (action.messageBlocks === undefined) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const clicked = (block: unknown): boolean =>
|
|
256
|
+
typeof block === "object" &&
|
|
257
|
+
block !== null &&
|
|
258
|
+
(block as { type?: unknown }).type === "actions" &&
|
|
259
|
+
(block as { block_id?: unknown }).block_id === action.blockId;
|
|
260
|
+
const result = await updateMessage({
|
|
261
|
+
botToken: args.botToken,
|
|
262
|
+
channel: action.channelId,
|
|
263
|
+
ts: action.messageTs,
|
|
264
|
+
text: note,
|
|
265
|
+
blocks: [
|
|
266
|
+
...action.messageBlocks.filter((block) => !clicked(block)),
|
|
267
|
+
{ type: "context", elements: [{ type: "mrkdwn", text: note }] },
|
|
268
|
+
],
|
|
269
|
+
});
|
|
270
|
+
return result.ok;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export interface SlackBlockActionDispatchDeps extends SlackContinuationProbes {
|
|
274
|
+
handler: SlackBlockActionHandler;
|
|
275
|
+
send: SendMessageFn;
|
|
276
|
+
credentials: SlackCredentials | undefined;
|
|
277
|
+
host: HostContext;
|
|
278
|
+
log?: SlackLog;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* The click as an inbound-message stand-in: the synthetic ts is the click's
|
|
283
|
+
* `action_ts` (unique per click, never a real thread message), so ask-dedupe
|
|
284
|
+
* claims and `<slack_thread>` exclusions stay per-click.
|
|
285
|
+
*/
|
|
286
|
+
function blockActionAsMessage(action: SlackBlockAction): SlackMessage {
|
|
287
|
+
const text = `[clicked: ${action.label ?? action.actionId}]`;
|
|
288
|
+
return {
|
|
289
|
+
text,
|
|
290
|
+
markdown: text,
|
|
291
|
+
ts: action.actionTs,
|
|
292
|
+
threadTs: action.threadTs,
|
|
293
|
+
channelId: action.channelId,
|
|
294
|
+
teamId: action.teamId,
|
|
295
|
+
author: {
|
|
296
|
+
userId: action.userId,
|
|
297
|
+
userName: action.userName,
|
|
298
|
+
isBot: false,
|
|
299
|
+
},
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Route a non-approval `block_actions` payload to the author handler and
|
|
305
|
+
* dispatch its decision as a turn on the click's thread. Returns true when
|
|
306
|
+
* the payload was a block action (dispatched or deliberately skipped).
|
|
307
|
+
*/
|
|
308
|
+
export async function dispatchBlockAction(
|
|
309
|
+
payload: unknown,
|
|
310
|
+
deps: SlackBlockActionDispatchDeps
|
|
311
|
+
): Promise<boolean> {
|
|
312
|
+
const log = deps.log ?? defaultSlackLog;
|
|
313
|
+
const action = parseSlackBlockAction(payload);
|
|
314
|
+
if (action === null) {
|
|
315
|
+
return false;
|
|
316
|
+
}
|
|
317
|
+
if (action.channelId === "" || action.threadTs === "") {
|
|
318
|
+
log.warn("block action skipped (no conversation)", {
|
|
319
|
+
action_id: action.actionId,
|
|
320
|
+
user: action.userId,
|
|
321
|
+
});
|
|
322
|
+
return true;
|
|
323
|
+
}
|
|
324
|
+
// Slack stamps every real click; without one the synthetic message ts
|
|
325
|
+
// would alias another click's dedupe claim.
|
|
326
|
+
if (action.actionTs === "") {
|
|
327
|
+
log.warn("block action skipped (no action_ts)", {
|
|
328
|
+
action_id: action.actionId,
|
|
329
|
+
user: action.userId,
|
|
330
|
+
});
|
|
331
|
+
return true;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
log.info("block action", {
|
|
335
|
+
action_id: action.actionId,
|
|
336
|
+
channel: action.channelId,
|
|
337
|
+
thread_ts: action.threadTs,
|
|
338
|
+
message_ts: action.messageTs,
|
|
339
|
+
user: action.userId,
|
|
340
|
+
});
|
|
341
|
+
|
|
342
|
+
await dispatchInboundMessage({
|
|
343
|
+
kind: "block_action",
|
|
344
|
+
message: blockActionAsMessage(action),
|
|
345
|
+
handler: (ctx) => deps.handler(ctx, action),
|
|
346
|
+
send: deps.send,
|
|
347
|
+
credentials: deps.credentials,
|
|
348
|
+
host: deps.host,
|
|
349
|
+
log,
|
|
350
|
+
// Click text is synthetic — never treat it as a stop/eval directive.
|
|
351
|
+
evalCommandsEnabled: false,
|
|
352
|
+
hasContinuationSession: deps.hasContinuationSession,
|
|
353
|
+
isContinuationBusy: deps.isContinuationBusy,
|
|
354
|
+
getContinuationLastBotMessageTs: deps.getContinuationLastBotMessageTs,
|
|
355
|
+
interruptContinuation: deps.interruptContinuation,
|
|
356
|
+
});
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Import at https://api.slack.com/apps -> Create New App -> From a manifest.
|
|
5
5
|
* Always Socket Mode — no Events Request URL.
|
|
6
|
+
*
|
|
7
|
+
* Mirrored by backend/server/src/agentServe/agentServeSlackManifest.ts (the
|
|
8
|
+
* backend cannot import this published package); keep the twins in lockstep.
|
|
6
9
|
*/
|
|
7
10
|
|
|
8
11
|
import type { SlackManifestOptions } from "./types.js";
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Slack setup guide printed by `agent-serve slack setup`.
|
|
3
3
|
*
|
|
4
|
-
* Mirrors the Cursor skill at skills/setup-slack/SKILL.md
|
|
4
|
+
* Mirrors the Cursor skill at skills/setup-slack/SKILL.md: connection
|
|
5
|
+
* decision first (Cursor-hosted vs dedicated Socket Mode app), then the
|
|
6
|
+
* dedicated-app phases. {@link SLACK_SETUP_PHASES} stays the dedicated-app
|
|
7
|
+
* checklist — its ids key `.agent-serve/slack/setup-status.json`.
|
|
5
8
|
*/
|
|
6
9
|
|
|
7
10
|
export interface SlackSetupPhase {
|
|
@@ -75,8 +78,19 @@ export const SLACK_SETUP_PHASES: SlackSetupPhase[] = [
|
|
|
75
78
|
|
|
76
79
|
export function formatSlackSetupGuide(): string {
|
|
77
80
|
const lines: string[] = [
|
|
78
|
-
"agent-serve — Slack setup
|
|
81
|
+
"agent-serve — Slack setup",
|
|
79
82
|
"",
|
|
83
|
+
"Two connections exist; decide before running the phases below.",
|
|
84
|
+
"",
|
|
85
|
+
" A) Cursor-hosted connection (recommended default, zero Slack admin):",
|
|
86
|
+
' slackChannel({ cursorAccount: true, agentName: "MyAgent" })',
|
|
87
|
+
" Sign in (agent-serve login / CURSOR_API_KEY), serve, then mention",
|
|
88
|
+
' "@Cursor MyAgent ..." in the linked workspace. No app, no tokens.',
|
|
89
|
+
" B) Dedicated Slack app (Socket Mode) — the phases below. Choose it",
|
|
90
|
+
" when you need your own bot user, channel watching",
|
|
91
|
+
" (engagement.channelPosts), or approval buttons / interactivity.",
|
|
92
|
+
"",
|
|
93
|
+
"Phases 0-5 cover connection B only.",
|
|
80
94
|
"Goal: importable dev + prod Slack apps over Socket Mode (no public URL).",
|
|
81
95
|
"App-Level Token (xapp-...) + Bot Token (xoxb-...) required.",
|
|
82
96
|
"",
|
|
@@ -45,7 +45,10 @@ import {
|
|
|
45
45
|
type SlackInboundDispatchDeps,
|
|
46
46
|
} from "./dispatch.js";
|
|
47
47
|
import { createEventIdDedupe } from "./event-id-dedupe.js";
|
|
48
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
dispatchBlockAction,
|
|
50
|
+
dispatchInteractiveFromPayload,
|
|
51
|
+
} from "./interactive.js";
|
|
49
52
|
import { createSlackLogger, type SlackLog } from "./log.js";
|
|
50
53
|
import {
|
|
51
54
|
type SlackSocketModeHandle,
|
|
@@ -120,6 +123,11 @@ export function slackChannel<const Name extends string = string>(
|
|
|
120
123
|
"slackChannel({ cursorAccount: true }) does not support interactivity — interactive Block Kit clicks are not relayed over the Cursor Slack connection. Interactivity is Socket Mode only."
|
|
121
124
|
);
|
|
122
125
|
}
|
|
126
|
+
if (options.onBlockAction !== undefined) {
|
|
127
|
+
throw new Error(
|
|
128
|
+
"slackChannel({ cursorAccount: true }) does not support onBlockAction — interactive Block Kit clicks are not relayed over the Cursor Slack connection. Block actions are Socket Mode only."
|
|
129
|
+
);
|
|
130
|
+
}
|
|
123
131
|
}
|
|
124
132
|
const accountTransport = cursorAccount
|
|
125
133
|
? createPendingSlackApiTransport()
|
|
@@ -151,7 +159,9 @@ export function slackChannel<const Name extends string = string>(
|
|
|
151
159
|
const eventIds = createEventIdDedupe();
|
|
152
160
|
const toolApprovals = options.toolApprovals === true;
|
|
153
161
|
const interactivity = options.interactivity === true;
|
|
154
|
-
const
|
|
162
|
+
const onBlockAction = options.onBlockAction;
|
|
163
|
+
const routeInteractive =
|
|
164
|
+
toolApprovals || interactivity || onBlockAction !== undefined;
|
|
155
165
|
|
|
156
166
|
const runtime: { log: SlackLog } = {
|
|
157
167
|
log: createSlackLogger(options.logger),
|
|
@@ -408,12 +418,33 @@ export function slackChannel<const Name extends string = string>(
|
|
|
408
418
|
};
|
|
409
419
|
if (routeInteractive) {
|
|
410
420
|
socketOptions.onInteractive = async (payload) => {
|
|
411
|
-
|
|
421
|
+
// Framework tool-approval Approve/Deny clicks resolve first;
|
|
422
|
+
// anything else (including approval-shaped clicks whose value
|
|
423
|
+
// fails to parse) falls through to the author's block-action hook.
|
|
424
|
+
const handledAsApproval = await dispatchInteractiveFromPayload(
|
|
425
|
+
payload,
|
|
426
|
+
{
|
|
427
|
+
credentials,
|
|
428
|
+
getSession: args.getSession,
|
|
429
|
+
resolveApproval: args.resolveApproval,
|
|
430
|
+
waitUntil: args.waitUntil,
|
|
431
|
+
log: getLog(),
|
|
432
|
+
}
|
|
433
|
+
);
|
|
434
|
+
if (handledAsApproval || onBlockAction === undefined) {
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
await dispatchBlockAction(payload, {
|
|
438
|
+
handler: onBlockAction,
|
|
439
|
+
send: args.send,
|
|
412
440
|
credentials,
|
|
413
|
-
|
|
414
|
-
resolveApproval: args.resolveApproval,
|
|
415
|
-
waitUntil: args.waitUntil,
|
|
441
|
+
host: args.host,
|
|
416
442
|
log: getLog(),
|
|
443
|
+
hasContinuationSession: args.hasContinuationSession,
|
|
444
|
+
isContinuationBusy: args.isContinuationBusy,
|
|
445
|
+
getContinuationLastBotMessageTs:
|
|
446
|
+
args.getContinuationLastBotMessageTs,
|
|
447
|
+
interruptContinuation: args.interruptContinuation,
|
|
417
448
|
});
|
|
418
449
|
};
|
|
419
450
|
}
|