@cursor/july 0.1.61 → 0.1.68
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/bin/agent-serve.js +8 -0
- package/dist/channels/github/github-channel.d.ts +1 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +14 -1
- package/dist/channels/github/types.d.ts +2 -0
- package/dist/channels/github/types.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.d.ts +2 -1
- package/dist/channels/slack/channel-watch.d.ts.map +1 -1
- package/dist/channels/slack/channel-watch.js +7 -3
- package/dist/channels/slack/dispatch.d.ts +9 -0
- package/dist/channels/slack/dispatch.d.ts.map +1 -1
- package/dist/channels/slack/dispatch.js +41 -21
- package/dist/channels/slack/manifest.d.ts.map +1 -1
- package/dist/channels/slack/manifest.js +1 -0
- package/dist/channels/slack/nudge-store.d.ts +16 -0
- package/dist/channels/slack/nudge-store.d.ts.map +1 -1
- package/dist/channels/slack/nudge-store.js +53 -0
- package/dist/channels/slack/slack-channel.d.ts.map +1 -1
- package/dist/channels/slack/slack-channel.js +4 -1
- package/dist/channels/slack/types.d.ts +22 -5
- 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.CW3KJh72.js → app.C5XUTCw9.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.DvUR3lv9.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.Bk6EWpPj.js → VPLocalSearchBox.CGDTAYHY.js} +1 -1
- package/dist/docs/assets/chunks/{theme.Bz9AodBA.js → theme.lPixh250.js} +2 -2
- package/dist/docs/assets/{reference_connections.md.CSQiNzre.js → reference_connections.md.Bwswbm6H.js} +13 -3
- package/dist/docs/assets/{reference_connections.md.CSQiNzre.lean.js → reference_connections.md.Bwswbm6H.lean.js} +1 -1
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.js → reference_tools.md.Wmvb7Lob.js} +2 -2
- package/dist/docs/assets/{reference_tools.md.kJfJAP2m.lean.js → reference_tools.md.Wmvb7Lob.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 +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 +3 -3
- package/dist/docs/reference/cli.html +3 -3
- package/dist/docs/reference/connections.html +15 -5
- 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 +5 -5
- 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/files-backends/agent-store-presigned-url.js +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/advertise-tools.d.ts +74 -0
- package/dist/internal/advertise-tools.d.ts.map +1 -0
- package/dist/internal/advertise-tools.js +152 -0
- package/dist/internal/cli-deploy.d.ts.map +1 -1
- package/dist/internal/cli-deploy.js +11 -3
- package/dist/internal/cli-manifest.d.ts +19 -0
- package/dist/internal/cli-manifest.d.ts.map +1 -0
- package/dist/internal/cli-manifest.js +49 -0
- package/dist/internal/cli-slack.d.ts.map +1 -1
- package/dist/internal/cli-slack.js +9 -1
- package/dist/internal/cursor-agent-template.d.ts +9 -0
- package/dist/internal/cursor-agent-template.d.ts.map +1 -0
- package/dist/internal/cursor-agent-template.js +37 -0
- package/dist/internal/cursor-event-relay.d.ts +5 -1
- package/dist/internal/cursor-event-relay.d.ts.map +1 -1
- package/dist/internal/cursor-event-relay.js +5 -0
- package/dist/internal/deploy-client.d.ts +8 -0
- package/dist/internal/deploy-client.d.ts.map +1 -1
- package/dist/internal/deploy-client.js +21 -3
- package/dist/internal/deploy-manifest.d.ts +85 -0
- package/dist/internal/deploy-manifest.d.ts.map +1 -0
- package/dist/internal/deploy-manifest.js +106 -0
- package/dist/internal/deployment-scm-event-stream.d.ts +113 -0
- package/dist/internal/deployment-scm-event-stream.d.ts.map +1 -0
- package/dist/internal/deployment-scm-event-stream.js +460 -0
- package/dist/internal/discovery.d.ts.map +1 -1
- package/dist/internal/discovery.js +69 -0
- package/dist/internal/init-project.d.ts.map +1 -1
- package/dist/internal/init-project.js +5 -17
- package/dist/internal/mcp-host.d.ts +18 -0
- package/dist/internal/mcp-host.d.ts.map +1 -1
- package/dist/internal/mcp-host.js +41 -5
- package/dist/internal/mcp-oauth.d.ts +0 -4
- package/dist/internal/mcp-oauth.d.ts.map +1 -1
- package/dist/internal/mcp-oauth.js +0 -4
- package/dist/internal/sdk-runner.d.ts +1 -1
- package/dist/internal/sdk-runner.d.ts.map +1 -1
- package/dist/internal/sdk-runner.js +36 -13
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +20 -2
- package/dist/internal/session-engine.d.ts +66 -0
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +430 -110
- package/dist/internal/shallow-clone.d.ts +115 -0
- package/dist/internal/shallow-clone.d.ts.map +1 -0
- package/dist/internal/shallow-clone.js +292 -0
- package/dist/internal/slack-provision-client.d.ts +2 -0
- package/dist/internal/slack-provision-client.d.ts.map +1 -1
- package/dist/internal/slack-provision-client.js +1 -0
- package/dist/internal/turn-lease-store.d.ts +44 -0
- package/dist/internal/turn-lease-store.d.ts.map +1 -0
- package/dist/internal/turn-lease-store.js +139 -0
- package/dist/internal/workspace.d.ts +12 -0
- package/dist/internal/workspace.d.ts.map +1 -1
- package/dist/internal/workspace.js +28 -0
- package/dist/playground/assets/index-CoL7xCJl.css +1 -0
- package/dist/playground/assets/index-yxs1heea.js +85 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-protocol.d.ts +122 -0
- package/dist/storage-protocol.d.ts.map +1 -0
- package/dist/storage-protocol.js +173 -0
- package/dist/storage.d.ts +2 -72
- package/dist/storage.d.ts.map +1 -1
- package/dist/storage.js +2 -82
- package/dist/types.d.ts +114 -4
- package/dist/types.d.ts.map +1 -1
- package/docs/reference/connections.md +80 -0
- package/docs/reference/tools.md +14 -0
- package/package.json +4 -2
- package/skills/github/SKILL.md +2 -1
- package/src/bin/agent-serve.ts +9 -0
- package/src/channels/github/github-channel.ts +18 -2
- package/src/channels/github/types.ts +2 -0
- package/src/channels/slack/channel-watch.ts +6 -3
- package/src/channels/slack/dispatch.ts +40 -3
- package/src/channels/slack/manifest.ts +1 -0
- package/src/channels/slack/nudge-store.ts +56 -0
- package/src/channels/slack/slack-channel.ts +3 -0
- package/src/channels/slack/types.ts +22 -5
- package/src/index.ts +5 -0
- package/src/internal/advertise-tools.ts +229 -0
- package/src/internal/cli-deploy.ts +22 -1
- package/src/internal/cli-manifest.ts +63 -0
- package/src/internal/cli-slack.ts +10 -1
- package/src/internal/cursor-agent-template.ts +46 -0
- package/src/internal/cursor-event-relay.ts +8 -1
- package/src/internal/deploy-client.ts +35 -6
- package/src/internal/deploy-manifest.ts +167 -0
- package/src/internal/deployment-scm-event-stream.ts +577 -0
- package/src/internal/discovery.ts +85 -0
- package/src/internal/init-project.ts +11 -20
- package/src/internal/mcp-host.ts +66 -9
- package/src/internal/mcp-oauth.ts +0 -4
- package/src/internal/sdk-runner.ts +50 -11
- package/src/internal/server.ts +21 -2
- package/src/internal/session-engine.ts +385 -19
- package/src/internal/shallow-clone.ts +407 -0
- package/src/internal/slack-provision-client.ts +3 -0
- package/src/internal/turn-lease-store.ts +147 -0
- package/src/internal/workspace.ts +27 -0
- package/src/storage-protocol.ts +247 -0
- package/src/storage.ts +16 -110
- package/src/types.ts +118 -4
- package/templates/pr-autofixer/agent/lib/drive-schemas.ts +4 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BuymfBAo.js +0 -1
- package/dist/playground/assets/index-ClbuaLpm.css +0 -1
- package/dist/playground/assets/index-SqkR13k_.js +0 -85
|
@@ -70,7 +70,7 @@ import type {
|
|
|
70
70
|
*/
|
|
71
71
|
type GitHubParsedContext = Omit<
|
|
72
72
|
GitHubEventContext,
|
|
73
|
-
"github" | "thread" | "host" | "artifacts"
|
|
73
|
+
"github" | "thread" | "host" | "artifacts" | "payload"
|
|
74
74
|
>;
|
|
75
75
|
|
|
76
76
|
const DEFAULT_PULL_REQUEST_ACTIONS: ReadonlySet<string> = new Set([
|
|
@@ -787,6 +787,7 @@ async function handlePullRequest(input: {
|
|
|
787
787
|
const ctx = enrichEventContext({
|
|
788
788
|
bindingConfig: input.bindingConfig,
|
|
789
789
|
ctx: parsed.ctx,
|
|
790
|
+
payload: input.payload,
|
|
790
791
|
bindingState: binding,
|
|
791
792
|
installationId,
|
|
792
793
|
host: input.args.host,
|
|
@@ -879,6 +880,7 @@ async function handleIssueComment(input: {
|
|
|
879
880
|
const ctx = enrichEventContext({
|
|
880
881
|
bindingConfig: input.bindingConfig,
|
|
881
882
|
ctx: parsed.ctx,
|
|
883
|
+
payload: input.payload,
|
|
882
884
|
bindingState: binding,
|
|
883
885
|
installationId,
|
|
884
886
|
host: input.args.host,
|
|
@@ -970,6 +972,7 @@ async function handleReviewComment(input: {
|
|
|
970
972
|
const ctx = enrichEventContext({
|
|
971
973
|
bindingConfig: input.bindingConfig,
|
|
972
974
|
ctx: parsed.ctx,
|
|
975
|
+
payload: input.payload,
|
|
973
976
|
bindingState: binding,
|
|
974
977
|
installationId,
|
|
975
978
|
host: input.args.host,
|
|
@@ -1050,6 +1053,7 @@ async function handleIssue(input: {
|
|
|
1050
1053
|
const ctx = enrichEventContext({
|
|
1051
1054
|
bindingConfig: input.bindingConfig,
|
|
1052
1055
|
ctx: parsed.ctx,
|
|
1056
|
+
payload: input.payload,
|
|
1053
1057
|
bindingState: binding,
|
|
1054
1058
|
installationId,
|
|
1055
1059
|
host: input.args.host,
|
|
@@ -1111,6 +1115,7 @@ async function handleCheckSuite(input: {
|
|
|
1111
1115
|
enrichCiContext({
|
|
1112
1116
|
bindingConfig: input.bindingConfig,
|
|
1113
1117
|
ctx: parsed.unanchoredCtx,
|
|
1118
|
+
payload: input.payload,
|
|
1114
1119
|
installationId,
|
|
1115
1120
|
host: input.args.host,
|
|
1116
1121
|
artifacts: input.args.artifacts,
|
|
@@ -1137,6 +1142,7 @@ async function handleCheckSuite(input: {
|
|
|
1137
1142
|
const ctx = enrichEventContext({
|
|
1138
1143
|
bindingConfig: input.bindingConfig,
|
|
1139
1144
|
ctx: parsed.ctx,
|
|
1145
|
+
payload: input.payload,
|
|
1140
1146
|
bindingState: binding,
|
|
1141
1147
|
installationId,
|
|
1142
1148
|
host: input.args.host,
|
|
@@ -1203,6 +1209,7 @@ async function handleCheckRun(input: {
|
|
|
1203
1209
|
enrichCiContext({
|
|
1204
1210
|
bindingConfig: input.bindingConfig,
|
|
1205
1211
|
ctx: parsed.unanchoredCtx,
|
|
1212
|
+
payload: input.payload,
|
|
1206
1213
|
installationId,
|
|
1207
1214
|
host: input.args.host,
|
|
1208
1215
|
artifacts: input.args.artifacts,
|
|
@@ -1229,6 +1236,7 @@ async function handleCheckRun(input: {
|
|
|
1229
1236
|
const ctx = enrichEventContext({
|
|
1230
1237
|
bindingConfig: input.bindingConfig,
|
|
1231
1238
|
ctx: parsed.ctx,
|
|
1239
|
+
payload: input.payload,
|
|
1232
1240
|
bindingState: binding,
|
|
1233
1241
|
installationId,
|
|
1234
1242
|
host: input.args.host,
|
|
@@ -1291,6 +1299,7 @@ async function handleWorkflowRun(input: {
|
|
|
1291
1299
|
enrichCiContext({
|
|
1292
1300
|
bindingConfig: input.bindingConfig,
|
|
1293
1301
|
ctx: parsed.unanchoredCtx,
|
|
1302
|
+
payload: input.payload,
|
|
1294
1303
|
installationId,
|
|
1295
1304
|
host: input.args.host,
|
|
1296
1305
|
artifacts: input.args.artifacts,
|
|
@@ -1317,6 +1326,7 @@ async function handleWorkflowRun(input: {
|
|
|
1317
1326
|
const ctx = enrichEventContext({
|
|
1318
1327
|
bindingConfig: input.bindingConfig,
|
|
1319
1328
|
ctx: parsed.ctx,
|
|
1329
|
+
payload: input.payload,
|
|
1320
1330
|
bindingState: binding,
|
|
1321
1331
|
installationId,
|
|
1322
1332
|
host: input.args.host,
|
|
@@ -1389,6 +1399,7 @@ async function handleStatus(input: {
|
|
|
1389
1399
|
const ctx = enrichEventContext({
|
|
1390
1400
|
bindingConfig: input.bindingConfig,
|
|
1391
1401
|
ctx: parsed.ctx,
|
|
1402
|
+
payload: input.payload,
|
|
1392
1403
|
bindingState: binding,
|
|
1393
1404
|
installationId,
|
|
1394
1405
|
host: input.args.host,
|
|
@@ -1463,6 +1474,7 @@ async function handleGenericEvent(input: {
|
|
|
1463
1474
|
const ctx = enrichEventContext({
|
|
1464
1475
|
bindingConfig: input.bindingConfig,
|
|
1465
1476
|
ctx: parsed.ctx,
|
|
1477
|
+
payload: input.payload,
|
|
1466
1478
|
bindingState: binding,
|
|
1467
1479
|
installationId,
|
|
1468
1480
|
host: input.args.host,
|
|
@@ -2353,10 +2365,11 @@ function escapeRegExp(value: string): string {
|
|
|
2353
2365
|
*/
|
|
2354
2366
|
function enrichEventContext(input: {
|
|
2355
2367
|
bindingConfig: GitHubBindingConfig;
|
|
2356
|
-
ctx:
|
|
2368
|
+
ctx: GitHubParsedContext &
|
|
2357
2369
|
Partial<Pick<GitHubEventContext, "github" | "thread">>;
|
|
2358
2370
|
bindingState: GitHubBindingState;
|
|
2359
2371
|
installationId?: number;
|
|
2372
|
+
payload: unknown;
|
|
2360
2373
|
host: HostContext;
|
|
2361
2374
|
artifacts: ArtifactsApi;
|
|
2362
2375
|
}): GitHubEventContext {
|
|
@@ -2366,6 +2379,7 @@ function enrichEventContext(input: {
|
|
|
2366
2379
|
});
|
|
2367
2380
|
return {
|
|
2368
2381
|
...input.ctx,
|
|
2382
|
+
payload: input.payload,
|
|
2369
2383
|
...(input.installationId === undefined
|
|
2370
2384
|
? {}
|
|
2371
2385
|
: { installationId: input.installationId }),
|
|
@@ -2384,6 +2398,7 @@ function enrichCiContext(input: {
|
|
|
2384
2398
|
bindingConfig: GitHubBindingConfig;
|
|
2385
2399
|
ctx: GitHubParsedContext;
|
|
2386
2400
|
installationId?: number;
|
|
2401
|
+
payload: unknown;
|
|
2387
2402
|
host: HostContext;
|
|
2388
2403
|
artifacts: ArtifactsApi;
|
|
2389
2404
|
}): GitHubEventContext {
|
|
@@ -2400,6 +2415,7 @@ function enrichCiContext(input: {
|
|
|
2400
2415
|
ctx: input.ctx,
|
|
2401
2416
|
bindingState: binding,
|
|
2402
2417
|
installationId: input.installationId,
|
|
2418
|
+
payload: input.payload,
|
|
2403
2419
|
host: input.host,
|
|
2404
2420
|
artifacts: input.artifacts,
|
|
2405
2421
|
});
|
|
@@ -320,6 +320,8 @@ export interface GitHubEventContext {
|
|
|
320
320
|
sender?: GitHubUser;
|
|
321
321
|
/** Installation id from the webhook payload, when present. */
|
|
322
322
|
installationId?: number;
|
|
323
|
+
/** Verified webhook body for this delivery. */
|
|
324
|
+
payload: unknown;
|
|
323
325
|
/** Authenticated GitHub helpers (`getOctokit`, `request`, `createStatus`). */
|
|
324
326
|
github: GitHubHandle;
|
|
325
327
|
/** Post / react on the conversation thread. */
|
|
@@ -21,10 +21,11 @@ import type { SlackChannelPostsOptions, SlackMessage } from "./types.js";
|
|
|
21
21
|
|
|
22
22
|
const CHANNEL_ID_PATTERN = /^[CG][A-Z0-9]+$/;
|
|
23
23
|
|
|
24
|
-
/** Allowlist over channel ids and `#names` (names resolve lazily)
|
|
24
|
+
/** Allowlist over channel ids and `#names` (names resolve lazily), or `*`. */
|
|
25
25
|
export class SlackChannelAllowlist {
|
|
26
26
|
private readonly ids = new Set<string>();
|
|
27
27
|
private readonly names = new Set<string>();
|
|
28
|
+
private all = false;
|
|
28
29
|
|
|
29
30
|
constructor(entries: readonly string[]) {
|
|
30
31
|
for (const raw of entries) {
|
|
@@ -32,7 +33,9 @@ export class SlackChannelAllowlist {
|
|
|
32
33
|
if (entry === "") {
|
|
33
34
|
continue;
|
|
34
35
|
}
|
|
35
|
-
if (entry
|
|
36
|
+
if (entry === "*") {
|
|
37
|
+
this.all = true;
|
|
38
|
+
} else if (entry.startsWith("#")) {
|
|
36
39
|
this.names.add(entry.slice(1).toLowerCase());
|
|
37
40
|
} else if (CHANNEL_ID_PATTERN.test(entry)) {
|
|
38
41
|
this.ids.add(entry);
|
|
@@ -44,7 +47,7 @@ export class SlackChannelAllowlist {
|
|
|
44
47
|
}
|
|
45
48
|
|
|
46
49
|
matches(channelId: string): boolean {
|
|
47
|
-
return this.ids.has(channelId);
|
|
50
|
+
return this.all || this.ids.has(channelId);
|
|
48
51
|
}
|
|
49
52
|
|
|
50
53
|
unresolvedNames(): string[] {
|
|
@@ -142,6 +142,13 @@ export interface SlackInboundDispatchDeps extends SlackContinuationProbes {
|
|
|
142
142
|
* dropped at parse time (loop safety).
|
|
143
143
|
*/
|
|
144
144
|
botMentionGate?: SlackBotMentionGate;
|
|
145
|
+
/**
|
|
146
|
+
* When false, do not write `assistant.threads.setStatus` on a coalesced
|
|
147
|
+
* follow-up. Defaults to `true`. Channels that report progress their own
|
|
148
|
+
* way turn this off: the chip has no owner to clear it once the default
|
|
149
|
+
* `message.completed` handler is replaced, so it lingers past the turn.
|
|
150
|
+
*/
|
|
151
|
+
followUpStatus?: boolean;
|
|
145
152
|
/**
|
|
146
153
|
* When false, skip the built-in `eval` directive. Defaults to true when
|
|
147
154
|
* `host.evals` is present.
|
|
@@ -195,6 +202,8 @@ export async function dispatchInboundMessage(args: {
|
|
|
195
202
|
log?: SlackLog;
|
|
196
203
|
eventId?: string;
|
|
197
204
|
evalCommandsEnabled?: boolean;
|
|
205
|
+
/** See {@link SlackInboundDispatchDeps.followUpStatus}. Defaults to `true`. */
|
|
206
|
+
followUpStatus?: boolean;
|
|
198
207
|
/**
|
|
199
208
|
* cursorAccount agent display name — stripped before parsing `eval …`
|
|
200
209
|
* (`OncallBuddy eval list`).
|
|
@@ -477,8 +486,9 @@ export async function dispatchInboundMessage(args: {
|
|
|
477
486
|
event_id: args.eventId,
|
|
478
487
|
}
|
|
479
488
|
);
|
|
480
|
-
// Coalesce admits leave the active turn running
|
|
481
|
-
//
|
|
489
|
+
// Coalesce admits leave the active turn running: the follow-up is queued
|
|
490
|
+
// for the carrier to inject at the next tool boundary, and anything it
|
|
491
|
+
// does not reach in time drains into the boundary turn instead.
|
|
482
492
|
if (session.coalesced === true) {
|
|
483
493
|
log.info("session busy; coalescing follow-up", {
|
|
484
494
|
kind,
|
|
@@ -487,8 +497,32 @@ export async function dispatchInboundMessage(args: {
|
|
|
487
497
|
continuation,
|
|
488
498
|
event_id: args.eventId,
|
|
489
499
|
});
|
|
500
|
+
// Queue it for mid-turn injection too. The carrier drains this at the
|
|
501
|
+
// running turn's next tool boundary; whatever it does not reach in time
|
|
502
|
+
// still drains into the boundary turn, deduped by sourceTs.
|
|
503
|
+
try {
|
|
504
|
+
await args.host.slackNudges?.append({
|
|
505
|
+
continuationKey: continuation,
|
|
506
|
+
sourceTs: message.ts,
|
|
507
|
+
text: media.userText,
|
|
508
|
+
...(message.author?.userName === undefined
|
|
509
|
+
? {}
|
|
510
|
+
: { author: message.author.userName }),
|
|
511
|
+
});
|
|
512
|
+
} catch (nudgeError) {
|
|
513
|
+
log.warn("nudge append failed", {
|
|
514
|
+
error:
|
|
515
|
+
nudgeError instanceof Error
|
|
516
|
+
? nudgeError.message
|
|
517
|
+
: String(nudgeError),
|
|
518
|
+
continuation,
|
|
519
|
+
event_id: args.eventId,
|
|
520
|
+
});
|
|
521
|
+
}
|
|
490
522
|
try {
|
|
491
|
-
|
|
523
|
+
if (args.followUpStatus !== false) {
|
|
524
|
+
await binding.thread.startTyping(FOLLOWUP_TYPING);
|
|
525
|
+
}
|
|
492
526
|
} catch (typingError) {
|
|
493
527
|
log.error(
|
|
494
528
|
`follow-up typing failed: ${
|
|
@@ -581,6 +615,7 @@ export function dispatchInboundFromEventBody(
|
|
|
581
615
|
log,
|
|
582
616
|
evalCommandsEnabled: deps.evalCommandsEnabled,
|
|
583
617
|
cursorAgentName: deps.cursorAgentName,
|
|
618
|
+
followUpStatus: deps.followUpStatus,
|
|
584
619
|
blockExternals: deps.blockExternals,
|
|
585
620
|
...(deps.hasContinuationSession === undefined
|
|
586
621
|
? {}
|
|
@@ -623,6 +658,7 @@ export function dispatchInboundFromEventBody(
|
|
|
623
658
|
log,
|
|
624
659
|
evalCommandsEnabled: deps.evalCommandsEnabled,
|
|
625
660
|
cursorAgentName: deps.cursorAgentName,
|
|
661
|
+
followUpStatus: deps.followUpStatus,
|
|
626
662
|
blockExternals: deps.blockExternals,
|
|
627
663
|
...(deps.hasContinuationSession === undefined
|
|
628
664
|
? {}
|
|
@@ -659,6 +695,7 @@ export function dispatchInboundFromEventBody(
|
|
|
659
695
|
log,
|
|
660
696
|
evalCommandsEnabled: false,
|
|
661
697
|
cursorAgentName: deps.cursorAgentName,
|
|
698
|
+
followUpStatus: deps.followUpStatus,
|
|
662
699
|
blockExternals: deps.blockExternals,
|
|
663
700
|
...(deps.hasContinuationSession === undefined
|
|
664
701
|
? {}
|
|
@@ -202,6 +202,24 @@ export class SlackNudgeStore {
|
|
|
202
202
|
return (await this.listPending(continuationKey)).length > 0;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* How many follow-ups have reached the agent on this thread. Monotonic
|
|
207
|
+
* within a continuation, so a caller can spot a delivery by comparing two
|
|
208
|
+
* readings — unlike {@link hasUndelivered}, which races the carrier: both
|
|
209
|
+
* run at the same tool boundary, and a queued nudge drained before the
|
|
210
|
+
* first reading never looks pending at all.
|
|
211
|
+
*/
|
|
212
|
+
async deliveredCount(continuationKey: string): Promise<number> {
|
|
213
|
+
if (continuationKey.trim() === "") {
|
|
214
|
+
return 0;
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
return (await readdir(this.deliveredDir(continuationKey))).length;
|
|
218
|
+
} catch {
|
|
219
|
+
return 0;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
205
223
|
/**
|
|
206
224
|
* Atomically move all pending nudges to delivered and return them.
|
|
207
225
|
* Already-delivered source ts values are dropped (exactly-once).
|
|
@@ -238,6 +256,44 @@ export class SlackNudgeStore {
|
|
|
238
256
|
return delivered;
|
|
239
257
|
}
|
|
240
258
|
|
|
259
|
+
/**
|
|
260
|
+
* Settle a follow-up the caller is handling itself, so the carrier cannot
|
|
261
|
+
* inject it again. Used by the boundary drain: a follow-up the carrier did
|
|
262
|
+
* not reach becomes the next turn's prompt, and without this its pending
|
|
263
|
+
* record survives into that turn and gets injected on top of the ask it
|
|
264
|
+
* already is.
|
|
265
|
+
*/
|
|
266
|
+
async markDelivered(
|
|
267
|
+
continuationKey: string,
|
|
268
|
+
sourceTs: string
|
|
269
|
+
): Promise<void> {
|
|
270
|
+
const key = continuationKey.trim();
|
|
271
|
+
const ts = sourceTs.trim();
|
|
272
|
+
if (key === "" || ts === "") {
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
const pending = await this.listPending(key);
|
|
276
|
+
const record = pending.find((item) => item.sourceTs === ts);
|
|
277
|
+
await mkdir(this.deliveredDir(key), { recursive: true });
|
|
278
|
+
const dest = this.deliveredPath(key, ts);
|
|
279
|
+
const tmp = `${dest}.${process.pid}.tmp`;
|
|
280
|
+
try {
|
|
281
|
+
await writeFile(
|
|
282
|
+
tmp,
|
|
283
|
+
`${JSON.stringify(record ?? { continuationKey: key, sourceTs: ts })}\n`,
|
|
284
|
+
"utf8"
|
|
285
|
+
);
|
|
286
|
+
await rename(tmp, dest);
|
|
287
|
+
} catch {
|
|
288
|
+
await rm(tmp, { force: true }).catch(() => {});
|
|
289
|
+
}
|
|
290
|
+
if (record !== undefined) {
|
|
291
|
+
await rm(join(this.pendingDir(key), `${record.id}.json`), {
|
|
292
|
+
force: true,
|
|
293
|
+
}).catch(() => {});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
241
297
|
async clear(continuationKey: string): Promise<void> {
|
|
242
298
|
await rm(this.continuationDir(continuationKey), {
|
|
243
299
|
recursive: true,
|
|
@@ -219,6 +219,7 @@ export function slackChannel<const Name extends string = string>(
|
|
|
219
219
|
const mentionsEnabled = engagement.mentions !== false;
|
|
220
220
|
const directMessagesEnabled = engagement.directMessages !== false;
|
|
221
221
|
const evalCommandsEnabled = options.evalCommands !== false;
|
|
222
|
+
const followUpStatus = options.followUpStatus !== false;
|
|
222
223
|
const channelPosts = engagement.channelPosts;
|
|
223
224
|
if (channelPosts !== undefined) {
|
|
224
225
|
const entries = channelPosts.allow.filter((entry) => entry.trim() !== "");
|
|
@@ -296,6 +297,7 @@ export function slackChannel<const Name extends string = string>(
|
|
|
296
297
|
mentionsEnabled,
|
|
297
298
|
directMessagesEnabled,
|
|
298
299
|
evalCommandsEnabled,
|
|
300
|
+
followUpStatus,
|
|
299
301
|
// cursorAccount: backend router already refuses externals.
|
|
300
302
|
blockExternals: cursorAccount ? false : blockExternals,
|
|
301
303
|
...(args.hasContinuationSession === undefined
|
|
@@ -484,6 +486,7 @@ export function slackChannel<const Name extends string = string>(
|
|
|
484
486
|
artifacts: args.artifacts,
|
|
485
487
|
log: getLog(),
|
|
486
488
|
evalCommandsEnabled,
|
|
489
|
+
followUpStatus,
|
|
487
490
|
blockExternals,
|
|
488
491
|
...(args.hasContinuationSession === undefined
|
|
489
492
|
? {}
|
|
@@ -330,11 +330,18 @@ export type SlackBlockActionHandler = (
|
|
|
330
330
|
*/
|
|
331
331
|
export interface SlackChannelPostsOptions {
|
|
332
332
|
/**
|
|
333
|
-
* Channels to watch: ids (`C…` / `G…`)
|
|
334
|
-
* (empty is a definition error).
|
|
335
|
-
* `conversations.list` when the channel
|
|
336
|
-
* of each watched channel to receive its
|
|
337
|
-
* `cursorAccount: true` rejects
|
|
333
|
+
* Channels to watch: ids (`C…` / `G…`), `#names`, or `"*"` for every
|
|
334
|
+
* channel the bot is in. Must be non-empty (empty is a definition error).
|
|
335
|
+
* `#names` are resolved to ids via `conversations.list` when the channel
|
|
336
|
+
* starts; the bot must be a member of each watched channel to receive its
|
|
337
|
+
* messages. Socket Mode only — `cursorAccount: true` rejects
|
|
338
|
+
* `engagement.channelPosts`.
|
|
339
|
+
*
|
|
340
|
+
* `"*"` suits agents whose handler decides per message (a thread the agent
|
|
341
|
+
* is already in, say) rather than per channel. Every delivered message then
|
|
342
|
+
* reaches {@link SlackChannelOptions.onChannelPost}, and each one costs a
|
|
343
|
+
* `users.info` external-sender check first unless
|
|
344
|
+
* {@link SlackAccessPolicy.blockExternals} is off.
|
|
338
345
|
*/
|
|
339
346
|
allow: string[];
|
|
340
347
|
/**
|
|
@@ -571,6 +578,16 @@ export interface SlackChannelOptions {
|
|
|
571
578
|
* Defaults to `true`.
|
|
572
579
|
*/
|
|
573
580
|
thinkingSteps?: boolean;
|
|
581
|
+
/**
|
|
582
|
+
* Write Slack's assistant status chip ("Saw your follow-up…") when a
|
|
583
|
+
* follow-up coalesces into a running turn. Defaults to `true`.
|
|
584
|
+
*
|
|
585
|
+
* Set to `false` on channels that report progress themselves: the chip is
|
|
586
|
+
* cleared by the default `message.completed` handler, so a channel that
|
|
587
|
+
* replaces that handler has nothing to take it down and it outlives the
|
|
588
|
+
* turn.
|
|
589
|
+
*/
|
|
590
|
+
followUpStatus?: boolean;
|
|
574
591
|
/**
|
|
575
592
|
* Opt in to Slack delivery for human-in-the-loop tool approvals.
|
|
576
593
|
*
|
package/src/index.ts
CHANGED
|
@@ -132,6 +132,8 @@ export type {
|
|
|
132
132
|
export { defineSchedule } from "./schedules.js";
|
|
133
133
|
export { defineSkill } from "./skills.js";
|
|
134
134
|
export type {
|
|
135
|
+
DurableSessionRecord,
|
|
136
|
+
DurableSessionSummary,
|
|
135
137
|
StorageABTable,
|
|
136
138
|
StorageCasTable,
|
|
137
139
|
StorageConfig,
|
|
@@ -144,8 +146,11 @@ export type {
|
|
|
144
146
|
} from "./storage.js";
|
|
145
147
|
export {
|
|
146
148
|
defineStorage,
|
|
149
|
+
isDurableSessionRecordKey,
|
|
150
|
+
parseDurableSessionRecord,
|
|
147
151
|
STORAGE_KEY_ROOT,
|
|
148
152
|
storageKeys,
|
|
153
|
+
summarizeDurableSessionRecord,
|
|
149
154
|
} from "./storage.js";
|
|
150
155
|
export { defineTool } from "./tools.js";
|
|
151
156
|
export type * from "./types.js";
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advertised connection tools (`defineConnection({ advertiseTools: true })`).
|
|
3
|
+
*
|
|
4
|
+
* The session engine calls {@link resolveAdvertisedTools} while building
|
|
5
|
+
* every local turn: for each advertising connection it lists the server's
|
|
6
|
+
* tools and synthesizes a named 1:1 passthrough server tool per entry —
|
|
7
|
+
* the server's own names, descriptions, and live schemas — merged into the
|
|
8
|
+
* authored `agent/tools/` catalog. Because it runs at turn-build time it
|
|
9
|
+
* covers every way a turn starts (dispatcher sends, follow-ups, reminder
|
|
10
|
+
* fires that call the engine directly) and survives process restarts,
|
|
11
|
+
* since nothing is parked in memory between turns.
|
|
12
|
+
*
|
|
13
|
+
* Connections with per-session `auth` are listed and called through a
|
|
14
|
+
* one-off client carrying that session's headers (the registry's cached
|
|
15
|
+
* per-name client would leak one session's identity into another);
|
|
16
|
+
* auth-less connections go through the shared `host.mcp` registry, which
|
|
17
|
+
* also covers serve-resolved transports (Cursor account bridges, peers).
|
|
18
|
+
*
|
|
19
|
+
* Everything here fails closed: a listing failure, an invalid tool name,
|
|
20
|
+
* or a name collision throws, and the engine fails the turn rather than
|
|
21
|
+
* running it with a silently shrunken toolset.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import type {
|
|
25
|
+
DiscoveredConnection,
|
|
26
|
+
DiscoveredTool,
|
|
27
|
+
HostMcpCallResult,
|
|
28
|
+
HostMcpToolInfo,
|
|
29
|
+
JsonSchemaObject,
|
|
30
|
+
ResolvedAgent,
|
|
31
|
+
SessionInfo,
|
|
32
|
+
ToolDefinition,
|
|
33
|
+
} from "../types.js";
|
|
34
|
+
import { brandDefinition } from "./brand.js";
|
|
35
|
+
import type { HostMcpOneOff } from "./mcp-host.js";
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* What the resolver needs from the host MCP registry: the shared cached
|
|
39
|
+
* clients for auth-less connections, and one-off clients (per-session
|
|
40
|
+
* headers, never cached) for auth'd ones. `McpHost` implements this.
|
|
41
|
+
*/
|
|
42
|
+
export interface AdvertiseHostMcp {
|
|
43
|
+
listTools(connectionName: string): Promise<HostMcpToolInfo[]>;
|
|
44
|
+
callTool(
|
|
45
|
+
connectionName: string,
|
|
46
|
+
toolName: string,
|
|
47
|
+
args?: Record<string, unknown>
|
|
48
|
+
): Promise<HostMcpCallResult>;
|
|
49
|
+
oneOff(connection: DiscoveredConnection): HostMcpOneOff;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* The rule advertised tool names must match — the same shape discovery
|
|
54
|
+
* enforces for authored tool filenames (its `NAME_PATTERN`), since both
|
|
55
|
+
* become the tool name the model sees.
|
|
56
|
+
*/
|
|
57
|
+
export const ADVERTISED_TOOL_NAME_PATTERN: RegExp =
|
|
58
|
+
/^[A-Za-z0-9][A-Za-z0-9_-]*$/;
|
|
59
|
+
|
|
60
|
+
const ANY_OBJECT_SCHEMA: JsonSchemaObject = {
|
|
61
|
+
type: "object",
|
|
62
|
+
additionalProperties: true,
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** How one advertising connection is listed and called. */
|
|
66
|
+
export interface AdvertisedToolSource {
|
|
67
|
+
listTools(): Promise<HostMcpToolInfo[]>;
|
|
68
|
+
callTool(
|
|
69
|
+
toolName: string,
|
|
70
|
+
args: Record<string, unknown>
|
|
71
|
+
): Promise<HostMcpCallResult>;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Resolve the advertised tools for one session. Returns `[]` when no
|
|
76
|
+
* connection advertises; throws (failing the turn) when a listing fails
|
|
77
|
+
* or a definition is invalid.
|
|
78
|
+
*/
|
|
79
|
+
export async function resolveAdvertisedTools(args: {
|
|
80
|
+
agent: ResolvedAgent;
|
|
81
|
+
session: SessionInfo;
|
|
82
|
+
hostMcp: AdvertiseHostMcp;
|
|
83
|
+
/** Test seam; defaults to {@link sourceForConnection}. */
|
|
84
|
+
sourceFor?: (
|
|
85
|
+
connection: DiscoveredConnection,
|
|
86
|
+
session: SessionInfo,
|
|
87
|
+
hostMcp: AdvertiseHostMcp
|
|
88
|
+
) => Promise<AdvertisedToolSource>;
|
|
89
|
+
}): Promise<DiscoveredTool[]> {
|
|
90
|
+
const advertising = args.agent.connections.filter(
|
|
91
|
+
(connection) => connection.advertiseTools === true
|
|
92
|
+
);
|
|
93
|
+
if (advertising.length === 0) {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
96
|
+
const sourceFor = args.sourceFor ?? sourceForConnection;
|
|
97
|
+
const claimed = new Set(args.agent.tools.map((tool) => tool.name));
|
|
98
|
+
const resolved: DiscoveredTool[] = [];
|
|
99
|
+
for (const connection of advertising) {
|
|
100
|
+
const source = await sourceFor(connection, args.session, args.hostMcp);
|
|
101
|
+
let listed: HostMcpToolInfo[];
|
|
102
|
+
try {
|
|
103
|
+
listed = await source.listTools();
|
|
104
|
+
} catch (error) {
|
|
105
|
+
throw new Error(
|
|
106
|
+
`Failed to list tools on advertised connection "${connection.name}": ${describeError(error)}`,
|
|
107
|
+
{ cause: error }
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
for (const info of listed) {
|
|
111
|
+
resolved.push(toDiscoveredTool(connection, info, claimed, source));
|
|
112
|
+
claimed.add(info.name);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return resolved;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The production source for one advertising connection: the shared
|
|
120
|
+
* `host.mcp` registry client for auth-less connections (which also covers
|
|
121
|
+
* serve-resolved transports — Cursor account bridges, peers), or a
|
|
122
|
+
* registry one-off with the session's headers substituted when the
|
|
123
|
+
* connection has per-session `auth`. The auth callback is evaluated once
|
|
124
|
+
* per turn, here; the one-off opens a fresh client per operation so one
|
|
125
|
+
* session's identity can never serve another's call.
|
|
126
|
+
*/
|
|
127
|
+
export async function sourceForConnection(
|
|
128
|
+
connection: DiscoveredConnection,
|
|
129
|
+
session: SessionInfo,
|
|
130
|
+
hostMcp: AdvertiseHostMcp
|
|
131
|
+
): Promise<AdvertisedToolSource> {
|
|
132
|
+
if (connection.auth === undefined) {
|
|
133
|
+
return {
|
|
134
|
+
listTools: () => hostMcp.listTools(connection.name),
|
|
135
|
+
callTool: (toolName, callArgs) =>
|
|
136
|
+
hostMcp.callTool(connection.name, toolName, callArgs),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
const transport = connection.transport;
|
|
140
|
+
if (!("url" in transport)) {
|
|
141
|
+
// Discovery rejects auth on non-URL transports; guard for callers that
|
|
142
|
+
// bypass discovery (hand-built projects).
|
|
143
|
+
throw new Error(
|
|
144
|
+
`Connection "${connection.name}" has per-session auth but no URL transport.`
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
const evaluated = await connection.auth(session);
|
|
148
|
+
return hostMcp.oneOff({
|
|
149
|
+
...connection,
|
|
150
|
+
transport: {
|
|
151
|
+
...transport,
|
|
152
|
+
headers: { ...transport.headers, ...evaluated.headers },
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function toDiscoveredTool(
|
|
158
|
+
connection: DiscoveredConnection,
|
|
159
|
+
info: HostMcpToolInfo,
|
|
160
|
+
claimed: ReadonlySet<string>,
|
|
161
|
+
source: AdvertisedToolSource
|
|
162
|
+
): DiscoveredTool {
|
|
163
|
+
if (!ADVERTISED_TOOL_NAME_PATTERN.test(info.name)) {
|
|
164
|
+
throw new Error(
|
|
165
|
+
`Advertised connection "${connection.name}" serves tool name "${info.name}", which must match ${ADVERTISED_TOOL_NAME_PATTERN} (it becomes the tool name the model sees).`
|
|
166
|
+
);
|
|
167
|
+
}
|
|
168
|
+
if (claimed.has(info.name)) {
|
|
169
|
+
throw new Error(
|
|
170
|
+
`Advertised connection "${connection.name}" serves tool "${info.name}", which collides with an authored agent/tools/ tool or another advertised connection.`
|
|
171
|
+
);
|
|
172
|
+
}
|
|
173
|
+
const description =
|
|
174
|
+
info.description === undefined || info.description.trim() === ""
|
|
175
|
+
? `${info.name} — MCP tool on the "${connection.name}" connection, forwarded 1:1.`
|
|
176
|
+
: info.description;
|
|
177
|
+
const inputSchema = info.inputSchema ?? ANY_OBJECT_SCHEMA;
|
|
178
|
+
const definition: ToolDefinition = brandDefinition("tool", {
|
|
179
|
+
description,
|
|
180
|
+
inputSchema,
|
|
181
|
+
execute: async (input: Record<string, unknown>) =>
|
|
182
|
+
formatMcpToolResult(
|
|
183
|
+
connection.name,
|
|
184
|
+
info.name,
|
|
185
|
+
await source.callTool(info.name, input ?? {})
|
|
186
|
+
),
|
|
187
|
+
} as unknown as ToolDefinition);
|
|
188
|
+
return {
|
|
189
|
+
name: info.name,
|
|
190
|
+
description,
|
|
191
|
+
inputSchema,
|
|
192
|
+
execution: "server",
|
|
193
|
+
needsApproval: false,
|
|
194
|
+
definition,
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Render an MCP call result for the model: text parts joined, non-text
|
|
200
|
+
* parts noted by type, `isError` surfaced as a thrown error so the turn
|
|
201
|
+
* records a tool error instead of error-shaped prose.
|
|
202
|
+
*/
|
|
203
|
+
export function formatMcpToolResult(
|
|
204
|
+
connectionName: string,
|
|
205
|
+
toolName: string,
|
|
206
|
+
result: HostMcpCallResult
|
|
207
|
+
): string {
|
|
208
|
+
const parts = result.content.map((item) =>
|
|
209
|
+
item.type === "text" && typeof item.text === "string"
|
|
210
|
+
? item.text
|
|
211
|
+
: `[${item.type} content omitted]`
|
|
212
|
+
);
|
|
213
|
+
const text = parts.join("\n").trim();
|
|
214
|
+
if (result.isError === true) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
text === "" ? `${connectionName} tool ${toolName} failed` : text
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
if (text !== "") {
|
|
220
|
+
return text;
|
|
221
|
+
}
|
|
222
|
+
return result.structuredContent !== undefined
|
|
223
|
+
? JSON.stringify(result.structuredContent, null, 2)
|
|
224
|
+
: "(empty result)";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function describeError(error: unknown): string {
|
|
228
|
+
return error instanceof Error ? error.message : String(error);
|
|
229
|
+
}
|