@cursor/july 0.1.24 → 0.1.26
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 +0 -0
- package/dist/channels/github/instrument.d.ts +20 -0
- package/dist/channels/github/instrument.d.ts.map +1 -0
- package/dist/docs/404.html +1 -1
- package/dist/docs/ab.html +2 -2
- package/dist/docs/assets/{app.CvSGaAxk.js → app.DYcC9FY-.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.BQTzJjR_.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.J1jJbCvs.js → VPLocalSearchBox.o4N_knTV.js} +1 -1
- package/dist/docs/assets/chunks/{theme.C7vfzr7h.js → theme.DQ-njyo0.js} +2 -2
- package/dist/docs/building-with-agents.html +2 -2
- package/dist/docs/concepts.html +2 -2
- package/dist/docs/deployment.html +2 -2
- package/dist/docs/evals.html +2 -2
- package/dist/docs/example-agents/approval-buddy.html +2 -2
- package/dist/docs/example-agents/benny.html +2 -2
- package/dist/docs/example-agents/bugbot.html +2 -2
- package/dist/docs/example-agents/codebase-wiki.html +2 -2
- package/dist/docs/example-agents/codeowners-review.html +2 -2
- package/dist/docs/example-agents/concierge.html +2 -2
- package/dist/docs/example-agents/fsd.html +2 -2
- package/dist/docs/example-agents/index.html +2 -2
- package/dist/docs/example-agents/knowledge-base.html +2 -2
- package/dist/docs/example-agents/oncall.html +2 -2
- package/dist/docs/example-agents/security-reviewer.html +2 -2
- package/dist/docs/example-agents/slack-agent.html +2 -2
- package/dist/docs/example-agents/weather-agent.html +2 -2
- package/dist/docs/guides/agent-to-agent.html +2 -2
- package/dist/docs/guides/cloud-runtime.html +2 -2
- package/dist/docs/guides/github.html +2 -2
- package/dist/docs/guides/human-in-the-loop.html +2 -2
- package/dist/docs/guides/mcp-oauth.html +2 -2
- package/dist/docs/guides/slack.html +2 -2
- package/dist/docs/guides/webhooks.html +2 -2
- package/dist/docs/hillclimbing.html +2 -2
- package/dist/docs/index.html +2 -2
- package/dist/docs/quickstart.html +2 -2
- package/dist/docs/reference/agent-config.html +2 -2
- package/dist/docs/reference/channels.html +2 -2
- package/dist/docs/reference/cli.html +2 -2
- package/dist/docs/reference/connections.html +2 -2
- package/dist/docs/reference/hooks.html +2 -2
- package/dist/docs/reference/http-api.html +2 -2
- package/dist/docs/reference/instructions.html +2 -2
- package/dist/docs/reference/playground.html +2 -2
- package/dist/docs/reference/project-layout.html +2 -2
- package/dist/docs/reference/prompt.html +2 -2
- package/dist/docs/reference/schedules.html +2 -2
- package/dist/docs/reference/sessions.html +2 -2
- package/dist/docs/reference/skills.html +2 -2
- package/dist/docs/reference/subagents.html +2 -2
- package/dist/docs/reference/tools.html +2 -2
- package/dist/docs/scaffolding-agents.html +2 -2
- package/dist/docs/storage.html +2 -2
- package/dist/docs/troubleshooting.html +2 -2
- package/dist/internal/json-dir-store.d.ts +32 -0
- package/dist/internal/json-dir-store.d.ts.map +1 -0
- package/dist/internal/json-dir-store.js +100 -0
- package/dist/playground/assets/index-CjOQ4hN9.css +1 -0
- package/dist/playground/assets/{index-DTG9OsPV.js → index-DqXdAFGa.js} +41 -41
- package/dist/playground/index.html +2 -2
- package/package.json +31 -24
- package/src/artifacts.ts +78 -0
- package/src/bin/agent-serve.version.test.ts +64 -0
- package/src/channels/github/api.test.ts +64 -0
- package/src/channels/github/auth.test.ts +105 -0
- package/src/channels/github/cursor-account.test.ts +204 -0
- package/src/channels/github/forward.test.ts +457 -0
- package/src/channels/github/github-channel.ts +19 -2
- package/src/channels/github/github.test.ts +985 -0
- package/src/channels/github/replay.test.ts +179 -0
- package/src/channels/github/types.ts +6 -0
- package/src/channels/slack/api.post-message.test.ts +148 -0
- package/src/channels/slack/api.ts +3 -1
- package/src/channels/slack/approvals.test.ts +328 -0
- package/src/channels/slack/block-actions.test.ts +469 -0
- package/src/channels/slack/bot-mentions.test.ts +267 -0
- package/src/channels/slack/channel-watch.test.ts +363 -0
- package/src/channels/slack/cursor-account.test.ts +301 -0
- package/src/channels/slack/cursor-account.ts +3 -1
- package/src/channels/slack/defaults.final-post.test.ts +182 -0
- package/src/channels/slack/dispatch.test.ts +875 -0
- package/src/channels/slack/dispatch.ts +12 -1
- package/src/channels/slack/eval-directive.test.ts +273 -0
- package/src/channels/slack/interactive.ts +3 -0
- package/src/channels/slack/message-body.test.ts +54 -0
- package/src/channels/slack/nudge-store.test.ts +143 -0
- package/src/channels/slack/slack-channel.ts +6 -0
- package/src/channels/slack/slack.test.ts +391 -0
- package/src/channels/slack/stop.test.ts +23 -0
- package/src/channels/slack/thread-context.test.ts +202 -0
- package/src/channels/slack/types.ts +6 -0
- package/src/evals/assertions.test.ts +617 -0
- package/src/evals/assertions.ts +37 -1
- package/src/evals/expect.test.ts +144 -0
- package/src/evals/judge.test.ts +181 -0
- package/src/evals/loaders.test.ts +132 -0
- package/src/evals/matchers.test.ts +95 -0
- package/src/evals/reporters.test.ts +303 -0
- package/src/evals/run-facts.test.ts +259 -0
- package/src/evals.ts +1 -0
- package/src/index.ts +1 -0
- package/src/internal/ab-snapshot.test.ts +325 -0
- package/src/internal/approval-gate.test.ts +49 -0
- package/src/internal/approvals.integration.test.ts +383 -0
- package/src/internal/artifacts-store.test.ts +283 -0
- package/src/internal/artifacts-store.ts +290 -0
- package/src/internal/authored-loaders.test.ts +31 -0
- package/src/internal/builtin-tools/artifacts.test.ts +247 -0
- package/src/internal/builtin-tools/artifacts.ts +74 -0
- package/src/internal/builtin-tools/index.ts +21 -0
- package/src/internal/builtin-tools/reminders.test.ts +201 -0
- package/src/internal/channel-route-schema.test.ts +294 -0
- package/src/internal/chat-attach.test.ts +262 -0
- package/src/internal/cli-deploy.test.ts +1991 -0
- package/src/internal/cli-docs.test.ts +161 -0
- package/src/internal/cli-mcp.test.ts +789 -0
- package/src/internal/cli-skills.test.ts +133 -0
- package/src/internal/cli-slack.test.ts +1654 -0
- package/src/internal/cli-slack.ts +13 -4
- package/src/internal/cloud-merge.test.ts +74 -0
- package/src/internal/cron.test.ts +22 -0
- package/src/internal/cursor/account-mcp.test.ts +807 -0
- package/src/internal/cursor/backend-client.test.ts +591 -0
- package/src/internal/cursor/credentials.test.ts +351 -0
- package/src/internal/cursor/github-credentials.test.ts +136 -0
- package/src/internal/cursor-account-mcp-auth.test.ts +310 -0
- package/src/internal/cursor-account.integration.test.ts +441 -0
- package/src/internal/cursor-event-relay.test.ts +746 -0
- package/src/internal/cursor-github-credentials.integration.test.ts +271 -0
- package/src/internal/cursor-slack-relay.test.ts +525 -0
- package/src/internal/deploy-source.test.ts +111 -0
- package/src/internal/discovery.artifact-tool.test.ts +136 -0
- package/src/internal/discovery.artifacts.test.ts +119 -0
- package/src/internal/discovery.builtin-tools.test.ts +94 -0
- package/src/internal/discovery.concurrency.test.ts +60 -0
- package/src/internal/discovery.cursor-account.test.ts +133 -0
- package/src/internal/discovery.cwd.test.ts +83 -0
- package/src/internal/discovery.hosting.test.ts +80 -0
- package/src/internal/discovery.identity.test.ts +44 -0
- package/src/internal/discovery.ts +102 -1
- package/src/internal/distribution.ts +1 -0
- package/src/internal/docs-site.test.ts +66 -0
- package/src/internal/duration.test.ts +29 -0
- package/src/internal/eval-judge-model.test.ts +187 -0
- package/src/internal/eval-run-store.cancel.test.ts +142 -0
- package/src/internal/eval-run-store.storage.test.ts +211 -0
- package/src/internal/eval-runner.http.test.ts +403 -0
- package/src/internal/eval-runner.run.test.ts +928 -0
- package/src/internal/evals-client.test.ts +307 -0
- package/src/internal/event-mapper.test.ts +243 -0
- package/src/internal/github-fanout.test.ts +213 -0
- package/src/internal/handleAgentServeTrigger.test.ts +185 -0
- package/src/internal/host-kv.test.ts +82 -0
- package/src/internal/host-platforms.test.ts +126 -0
- package/src/internal/http-channel.test.ts +402 -0
- package/src/internal/init-project.test.ts +270 -0
- package/src/internal/install-cursor-skills.test.ts +262 -0
- package/src/internal/local-env.test.ts +120 -0
- package/src/internal/log-ring.test.ts +31 -0
- package/src/internal/logs-client.test.ts +350 -0
- package/src/internal/mcp-endpoint.test.ts +436 -0
- package/src/internal/mcp-host.test.ts +298 -0
- package/src/internal/mcp-oauth.test.ts +148 -0
- package/src/internal/net.test.ts +17 -0
- package/src/internal/peer-connections.test.ts +128 -0
- package/src/internal/peer-mcp.integration.test.ts +289 -0
- package/src/internal/playground/toolchain.test.ts +53 -0
- package/src/internal/playground-cli.test.ts +187 -0
- package/src/internal/playground-proxy.test.ts +376 -0
- package/src/internal/prompt-context.integration.test.ts +232 -0
- package/src/internal/prompt-context.test.ts +127 -0
- package/src/internal/reminder-runner.test.ts +432 -0
- package/src/internal/reminder-runner.ts +1 -0
- package/src/internal/reminder-store.test.ts +53 -0
- package/src/internal/request-headers.test.ts +27 -0
- package/src/internal/resolve-prod-target.test.ts +787 -0
- package/src/internal/resolved-connections.test.ts +295 -0
- package/src/internal/router.test.ts +57 -0
- package/src/internal/schedule-runner.ts +1 -0
- package/src/internal/sdk-runner.test.ts +290 -0
- package/src/internal/server.artifacts.test.ts +334 -0
- package/src/internal/server.ts +45 -0
- package/src/internal/session-engine.artifacts.test.ts +293 -0
- package/src/internal/session-engine.coalesce.test.ts +169 -0
- package/src/internal/session-engine.concurrency.test.ts +250 -0
- package/src/internal/session-engine.host-oauth-mcp.test.ts +110 -0
- package/src/internal/session-engine.interrupt.test.ts +577 -0
- package/src/internal/session-engine.storage.test.ts +547 -0
- package/src/internal/session-engine.ts +129 -1
- package/src/internal/session-urls.test.ts +28 -0
- package/src/internal/sessions-client.test.ts +518 -0
- package/src/internal/storage-coordinator.test.ts +517 -0
- package/src/internal/storage-coordinator.ts +62 -0
- package/src/internal/tool-call.test.ts +458 -0
- package/src/internal/tool-result.test.ts +52 -0
- package/src/internal/trajectory.approvals.test.ts +83 -0
- package/src/internal/trajectory.subagents.test.ts +198 -0
- package/src/internal/turn-governor.test.ts +137 -0
- package/src/internal/update-check.test.ts +485 -0
- package/src/internal/workspace.test.ts +207 -0
- package/src/storage-backends/cursor-hosted.test.ts +121 -0
- package/src/storage.ts +6 -0
- package/src/types.ts +152 -2
- package/dist/docs/assets/chunks/@localSearchIndexroot.CL2Y0Zmh.js +0 -1
- package/dist/playground/assets/index-CidizGZv.css +0 -1
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { setDevMode } from "../../internal/dev-mode.js";
|
|
3
|
+
import type { AgentProject, DiscoveredChannel } from "../../types.js";
|
|
4
|
+
import { deriveGitHubWebhookEvents, GITHUB_CHANNEL_KIND } from "./events.js";
|
|
5
|
+
import {
|
|
6
|
+
type AgentMountLike,
|
|
7
|
+
buildGitHubForwardArgs,
|
|
8
|
+
channelForwardUrl,
|
|
9
|
+
detectGitHubEnvTokenOverride,
|
|
10
|
+
findGitHubChannelTargets,
|
|
11
|
+
GITHUB_WEBHOOK_EXTENSION_PIN,
|
|
12
|
+
ghWebhookExtensionInstallArgs,
|
|
13
|
+
ghWebhookExtensionPinSatisfied,
|
|
14
|
+
parseGhWebhookExtensionInstalled,
|
|
15
|
+
parseGhWebhookExtensionVersion,
|
|
16
|
+
parseRepoFromRemoteUrl,
|
|
17
|
+
resolveGitHubForwardTargets,
|
|
18
|
+
unionForwardEvents,
|
|
19
|
+
} from "./forward.js";
|
|
20
|
+
import { githubChannel } from "./github-channel.js";
|
|
21
|
+
|
|
22
|
+
describe("deriveGitHubWebhookEvents", () => {
|
|
23
|
+
it("returns the base set for a hookless channel", () => {
|
|
24
|
+
expect(deriveGitHubWebhookEvents({})).toEqual([
|
|
25
|
+
"pull_request",
|
|
26
|
+
"issue_comment",
|
|
27
|
+
"pull_request_review_comment",
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("adds an event per declared typed hook", () => {
|
|
32
|
+
const events = deriveGitHubWebhookEvents({
|
|
33
|
+
onCheckSuite: () => null,
|
|
34
|
+
onStatus: () => null,
|
|
35
|
+
});
|
|
36
|
+
expect(events).toContain("check_suite");
|
|
37
|
+
expect(events).toContain("status");
|
|
38
|
+
expect(events).not.toContain("check_run");
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it("adds the review catch-all events when onEvent is declared", () => {
|
|
42
|
+
const events = deriveGitHubWebhookEvents({ onEvent: () => null });
|
|
43
|
+
expect(events).toContain("pull_request_review");
|
|
44
|
+
expect(events).toContain("pull_request_review_thread");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("treats webhookEvents as authoritative (deduped)", () => {
|
|
48
|
+
expect(
|
|
49
|
+
deriveGitHubWebhookEvents({
|
|
50
|
+
webhookEvents: ["pull_request", "pull_request", " status "],
|
|
51
|
+
onCheckRun: () => null,
|
|
52
|
+
})
|
|
53
|
+
).toEqual(["pull_request", "status"]);
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
describe("githubChannel meta", () => {
|
|
58
|
+
it("publishes the github kind and derived events", () => {
|
|
59
|
+
const channel = githubChannel({ onCheckRun: () => null });
|
|
60
|
+
expect(channel.meta?.kind).toBe(GITHUB_CHANNEL_KIND);
|
|
61
|
+
expect(channel.meta?.githubWebhookEvents).toContain("check_run");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("honors an explicit webhookEvents pin", () => {
|
|
65
|
+
const channel = githubChannel({ webhookEvents: ["pull_request"] });
|
|
66
|
+
expect(channel.meta?.githubWebhookEvents).toEqual(["pull_request"]);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe("githubChannel dev auth default", () => {
|
|
71
|
+
const savedWebhookSecret = process.env.GITHUB_WEBHOOK_SECRET;
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
delete process.env.GITHUB_WEBHOOK_SECRET;
|
|
74
|
+
});
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
setDevMode(false);
|
|
77
|
+
if (savedWebhookSecret === undefined) {
|
|
78
|
+
delete process.env.GITHUB_WEBHOOK_SECRET;
|
|
79
|
+
} else {
|
|
80
|
+
process.env.GITHUB_WEBHOOK_SECRET = savedWebhookSecret;
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const forwarded = new Request("http://example.com/hook");
|
|
84
|
+
const nonLoopback = { requestIp: "203.0.113.4" };
|
|
85
|
+
const loopback = { requestIp: "127.0.0.1" };
|
|
86
|
+
|
|
87
|
+
it("admits loopback deliveries in dev without a secret", async () => {
|
|
88
|
+
setDevMode(true);
|
|
89
|
+
const policy = githubChannel().auth?.[0];
|
|
90
|
+
expect(
|
|
91
|
+
await policy?.(new Request("http://127.0.0.1/hook"), loopback)
|
|
92
|
+
).not.toBeNull();
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("admits loopback deliveries carrying gh forwarding headers in dev", async () => {
|
|
96
|
+
// `gh webhook forward` relays GitHub's edge X-Forwarded-For / X-Real-Ip;
|
|
97
|
+
// dev auth must admit them from a loopback peer (localDev/localDevStrict
|
|
98
|
+
// would reject on those headers and break forwarding).
|
|
99
|
+
setDevMode(true);
|
|
100
|
+
const policy = githubChannel().auth?.[0];
|
|
101
|
+
const ghForwarded = new Request("http://127.0.0.1/hook", {
|
|
102
|
+
headers: {
|
|
103
|
+
"x-github-event": "pull_request",
|
|
104
|
+
"x-forwarded-for": "140.82.115.1",
|
|
105
|
+
"x-real-ip": "140.82.115.1",
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
expect(await policy?.(ghForwarded, loopback)).not.toBeNull();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("rejects loopback tunneled traffic with forwarding headers but no GitHub delivery headers in dev", async () => {
|
|
112
|
+
setDevMode(true);
|
|
113
|
+
const policy = githubChannel().auth?.[0];
|
|
114
|
+
const tunneled = new Request("http://127.0.0.1/hook", {
|
|
115
|
+
headers: {
|
|
116
|
+
"x-forwarded-for": "203.0.113.4",
|
|
117
|
+
"x-real-ip": "203.0.113.4",
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
expect(await policy?.(tunneled, loopback)).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("rejects non-loopback deliveries in dev without a secret", async () => {
|
|
124
|
+
setDevMode(true);
|
|
125
|
+
const policy = githubChannel().auth?.[0];
|
|
126
|
+
expect(await policy?.(forwarded, nonLoopback)).toBeNull();
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("stays loopback-strict outside dev without a secret", async () => {
|
|
130
|
+
setDevMode(false);
|
|
131
|
+
const policy = githubChannel().auth?.[0];
|
|
132
|
+
expect(await policy?.(forwarded, nonLoopback)).toBeNull();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("re-evaluates isDevMode per request on a cached channel definition", async () => {
|
|
136
|
+
// Channel modules call githubChannel() once at import; flipping --dev
|
|
137
|
+
// later must still change auth behavior for the same policy closure.
|
|
138
|
+
setDevMode(false);
|
|
139
|
+
const policy = githubChannel().auth?.[0];
|
|
140
|
+
const ghForwarded = new Request("http://127.0.0.1/hook", {
|
|
141
|
+
headers: {
|
|
142
|
+
"x-github-event": "pull_request",
|
|
143
|
+
"x-forwarded-for": "140.82.115.1",
|
|
144
|
+
"x-real-ip": "140.82.115.1",
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
// Outside --dev, localDevStrict rejects proxy-forwarded headers.
|
|
148
|
+
expect(await policy?.(ghForwarded, loopback)).toBeNull();
|
|
149
|
+
setDevMode(true);
|
|
150
|
+
// In --dev, loopbackGitHubDev admits gh webhook forward deliveries.
|
|
151
|
+
expect(await policy?.(ghForwarded, loopback)).not.toBeNull();
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
describe("findGitHubChannelTargets", () => {
|
|
156
|
+
it("collects github channels across mounts and ignores others", () => {
|
|
157
|
+
const mounts: AgentMountLike[] = [
|
|
158
|
+
mount("fsd", [
|
|
159
|
+
channel("github", githubChannel({ onCheckSuite: () => null })),
|
|
160
|
+
channel("drive", { __agentServe: "channel", routes: [] }),
|
|
161
|
+
]),
|
|
162
|
+
mount("reviewer", [
|
|
163
|
+
channel(
|
|
164
|
+
"github",
|
|
165
|
+
githubChannel({ webhookEvents: ["pull_request"], path: "/hook" })
|
|
166
|
+
),
|
|
167
|
+
]),
|
|
168
|
+
];
|
|
169
|
+
|
|
170
|
+
const targets = findGitHubChannelTargets(mounts);
|
|
171
|
+
expect(targets).toHaveLength(2);
|
|
172
|
+
expect(targets[0]).toMatchObject({
|
|
173
|
+
slug: "fsd",
|
|
174
|
+
channelId: "github",
|
|
175
|
+
routePath: "/",
|
|
176
|
+
});
|
|
177
|
+
expect(targets[0]?.events).toContain("check_suite");
|
|
178
|
+
expect(targets[1]).toMatchObject({
|
|
179
|
+
slug: "reviewer",
|
|
180
|
+
channelId: "github",
|
|
181
|
+
routePath: "/hook",
|
|
182
|
+
events: ["pull_request"],
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("channelForwardUrl", () => {
|
|
188
|
+
it("omits the slug prefix and the default path", () => {
|
|
189
|
+
expect(
|
|
190
|
+
channelForwardUrl({
|
|
191
|
+
host: "127.0.0.1",
|
|
192
|
+
port: 3000,
|
|
193
|
+
slug: "",
|
|
194
|
+
channelId: "github",
|
|
195
|
+
routePath: "/",
|
|
196
|
+
})
|
|
197
|
+
).toBe("http://127.0.0.1:3000/v1/channels/github");
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("includes the slug and a custom path", () => {
|
|
201
|
+
expect(
|
|
202
|
+
channelForwardUrl({
|
|
203
|
+
host: "localhost",
|
|
204
|
+
port: 4000,
|
|
205
|
+
slug: "fsd",
|
|
206
|
+
channelId: "github",
|
|
207
|
+
routePath: "/hook",
|
|
208
|
+
})
|
|
209
|
+
).toBe("http://localhost:4000/fsd/v1/channels/github/hook");
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
describe("resolveGitHubForwardTargets", () => {
|
|
214
|
+
const target = {
|
|
215
|
+
slug: "fsd",
|
|
216
|
+
channelId: "github",
|
|
217
|
+
routePath: "/",
|
|
218
|
+
events: ["pull_request", "check_run"],
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
it("derives the url and events from a single target", () => {
|
|
222
|
+
const result = resolveGitHubForwardTargets({
|
|
223
|
+
targets: [target],
|
|
224
|
+
host: "127.0.0.1",
|
|
225
|
+
port: 3000,
|
|
226
|
+
});
|
|
227
|
+
expect(result).toEqual({
|
|
228
|
+
ok: true,
|
|
229
|
+
targets: [
|
|
230
|
+
{
|
|
231
|
+
slug: "fsd",
|
|
232
|
+
channelId: "github",
|
|
233
|
+
url: "http://127.0.0.1:3000/fsd/v1/channels/github",
|
|
234
|
+
events: ["pull_request", "check_run"],
|
|
235
|
+
},
|
|
236
|
+
],
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("returns every matching channel for fan-out", () => {
|
|
241
|
+
const reviewer = {
|
|
242
|
+
slug: "reviewer",
|
|
243
|
+
channelId: "github",
|
|
244
|
+
routePath: "/",
|
|
245
|
+
events: ["pull_request"],
|
|
246
|
+
};
|
|
247
|
+
const result = resolveGitHubForwardTargets({
|
|
248
|
+
targets: [target, reviewer],
|
|
249
|
+
host: "127.0.0.1",
|
|
250
|
+
port: 3000,
|
|
251
|
+
});
|
|
252
|
+
expect(result.ok && result.targets.map((t) => t.slug)).toEqual([
|
|
253
|
+
"fsd",
|
|
254
|
+
"reviewer",
|
|
255
|
+
]);
|
|
256
|
+
expect(result.ok && unionForwardEvents(result.targets)).toEqual([
|
|
257
|
+
"pull_request",
|
|
258
|
+
"check_run",
|
|
259
|
+
]);
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("applies an explicit event override to all targets", () => {
|
|
263
|
+
const result = resolveGitHubForwardTargets({
|
|
264
|
+
targets: [target, { ...target, slug: "reviewer" }],
|
|
265
|
+
host: "127.0.0.1",
|
|
266
|
+
port: 3000,
|
|
267
|
+
events: ["issues"],
|
|
268
|
+
});
|
|
269
|
+
expect(result.ok && result.targets.map((t) => t.events)).toEqual([
|
|
270
|
+
["issues"],
|
|
271
|
+
["issues"],
|
|
272
|
+
]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("narrows by slug and channel", () => {
|
|
276
|
+
const other = { ...target, slug: "reviewer", channelId: "gh2" };
|
|
277
|
+
const result = resolveGitHubForwardTargets({
|
|
278
|
+
targets: [target, other],
|
|
279
|
+
slug: "reviewer",
|
|
280
|
+
channel: "gh2",
|
|
281
|
+
host: "127.0.0.1",
|
|
282
|
+
port: 3000,
|
|
283
|
+
});
|
|
284
|
+
expect(result.ok && result.targets).toHaveLength(1);
|
|
285
|
+
expect(result.ok && result.targets[0]?.channelId).toBe("gh2");
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it("uses an explicit url with a single target's events", () => {
|
|
289
|
+
const result = resolveGitHubForwardTargets({
|
|
290
|
+
targets: [target],
|
|
291
|
+
host: "127.0.0.1",
|
|
292
|
+
port: 3000,
|
|
293
|
+
url: "https://example.ngrok.app/hook",
|
|
294
|
+
});
|
|
295
|
+
expect(result.ok && result.targets[0]?.url).toBe(
|
|
296
|
+
"https://example.ngrok.app/hook"
|
|
297
|
+
);
|
|
298
|
+
expect(result.ok && result.targets[0]?.events).toEqual([
|
|
299
|
+
"pull_request",
|
|
300
|
+
"check_run",
|
|
301
|
+
]);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("rejects --url when several channels match", () => {
|
|
305
|
+
const result = resolveGitHubForwardTargets({
|
|
306
|
+
targets: [target, { ...target, slug: "reviewer" }],
|
|
307
|
+
host: "127.0.0.1",
|
|
308
|
+
port: 3000,
|
|
309
|
+
url: "https://example.ngrok.app/hook",
|
|
310
|
+
});
|
|
311
|
+
expect(result.ok).toBe(false);
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("requires --events for --url when no single target matches", () => {
|
|
315
|
+
const result = resolveGitHubForwardTargets({
|
|
316
|
+
targets: [],
|
|
317
|
+
host: "127.0.0.1",
|
|
318
|
+
port: 3000,
|
|
319
|
+
url: "https://example.ngrok.app/hook",
|
|
320
|
+
});
|
|
321
|
+
expect(result.ok).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it("errors when no github channel exists", () => {
|
|
325
|
+
const result = resolveGitHubForwardTargets({
|
|
326
|
+
targets: [],
|
|
327
|
+
host: "127.0.0.1",
|
|
328
|
+
port: 3000,
|
|
329
|
+
});
|
|
330
|
+
expect(result.ok).toBe(false);
|
|
331
|
+
expect(!result.ok && result.error).toContain("no GitHub channel found");
|
|
332
|
+
});
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
describe("buildGitHubForwardArgs", () => {
|
|
336
|
+
it("builds repo + events + url args", () => {
|
|
337
|
+
expect(
|
|
338
|
+
buildGitHubForwardArgs({
|
|
339
|
+
url: "http://127.0.0.1:3000/v1/channels/github",
|
|
340
|
+
events: ["pull_request", "issue_comment"],
|
|
341
|
+
repo: "acme/widgets",
|
|
342
|
+
})
|
|
343
|
+
).toEqual([
|
|
344
|
+
"webhook",
|
|
345
|
+
"forward",
|
|
346
|
+
"--url=http://127.0.0.1:3000/v1/channels/github",
|
|
347
|
+
"--events=pull_request,issue_comment",
|
|
348
|
+
"--repo=acme/widgets",
|
|
349
|
+
]);
|
|
350
|
+
});
|
|
351
|
+
|
|
352
|
+
it("supports org + secret and dedupes events", () => {
|
|
353
|
+
const args = buildGitHubForwardArgs({
|
|
354
|
+
url: "http://127.0.0.1:3000/v1/channels/github",
|
|
355
|
+
events: ["pull_request", "pull_request"],
|
|
356
|
+
org: "octo-org",
|
|
357
|
+
secret: "s3cr3t",
|
|
358
|
+
});
|
|
359
|
+
expect(args).toContain("--org=octo-org");
|
|
360
|
+
expect(args).toContain("--secret=s3cr3t");
|
|
361
|
+
expect(args).toContain("--events=pull_request");
|
|
362
|
+
});
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
describe("parseGhWebhookExtensionInstalled", () => {
|
|
366
|
+
it("detects the extension in gh extension list output", () => {
|
|
367
|
+
expect(
|
|
368
|
+
parseGhWebhookExtensionInstalled("gh webhook\tcli/gh-webhook\tv1.0.0\n")
|
|
369
|
+
).toBe(true);
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("returns false when absent", () => {
|
|
373
|
+
expect(parseGhWebhookExtensionInstalled("gh dash\tdlvhdr/gh-dash\n")).toBe(
|
|
374
|
+
false
|
|
375
|
+
);
|
|
376
|
+
});
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
describe("gh webhook extension pin", () => {
|
|
380
|
+
const listLine = `gh webhook\tcli/gh-webhook\t${GITHUB_WEBHOOK_EXTENSION_PIN}\n`;
|
|
381
|
+
|
|
382
|
+
it("parses the installed version", () => {
|
|
383
|
+
expect(parseGhWebhookExtensionVersion(listLine)).toBe(
|
|
384
|
+
GITHUB_WEBHOOK_EXTENSION_PIN
|
|
385
|
+
);
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("reports pin satisfaction", () => {
|
|
389
|
+
expect(ghWebhookExtensionPinSatisfied(listLine)).toBe(true);
|
|
390
|
+
expect(
|
|
391
|
+
ghWebhookExtensionPinSatisfied("gh webhook\tcli/gh-webhook\tv0.1.0\n")
|
|
392
|
+
).toBe(false);
|
|
393
|
+
});
|
|
394
|
+
|
|
395
|
+
it("builds pinned install argv", () => {
|
|
396
|
+
expect(ghWebhookExtensionInstallArgs()).toEqual([
|
|
397
|
+
"extension",
|
|
398
|
+
"install",
|
|
399
|
+
"cli/gh-webhook",
|
|
400
|
+
"--pin",
|
|
401
|
+
GITHUB_WEBHOOK_EXTENSION_PIN,
|
|
402
|
+
]);
|
|
403
|
+
expect(ghWebhookExtensionInstallArgs({ force: true })).toContain("--force");
|
|
404
|
+
});
|
|
405
|
+
});
|
|
406
|
+
|
|
407
|
+
describe("detectGitHubEnvTokenOverride", () => {
|
|
408
|
+
it("flags GITHUB_TOKEN, then GH_TOKEN", () => {
|
|
409
|
+
expect(detectGitHubEnvTokenOverride({ GITHUB_TOKEN: "x" })).toBe(
|
|
410
|
+
"GITHUB_TOKEN"
|
|
411
|
+
);
|
|
412
|
+
expect(detectGitHubEnvTokenOverride({ GH_TOKEN: "x" })).toBe("GH_TOKEN");
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
it("ignores empty/absent tokens", () => {
|
|
416
|
+
expect(detectGitHubEnvTokenOverride({})).toBeUndefined();
|
|
417
|
+
expect(
|
|
418
|
+
detectGitHubEnvTokenOverride({ GITHUB_TOKEN: "", GH_TOKEN: "" })
|
|
419
|
+
).toBeUndefined();
|
|
420
|
+
});
|
|
421
|
+
});
|
|
422
|
+
|
|
423
|
+
describe("parseRepoFromRemoteUrl", () => {
|
|
424
|
+
it("parses https, ssh, and git@ remotes", () => {
|
|
425
|
+
expect(parseRepoFromRemoteUrl("https://github.com/acme/widgets.git")).toBe(
|
|
426
|
+
"acme/widgets"
|
|
427
|
+
);
|
|
428
|
+
expect(parseRepoFromRemoteUrl("git@github.com:acme/widgets.git")).toBe(
|
|
429
|
+
"acme/widgets"
|
|
430
|
+
);
|
|
431
|
+
expect(parseRepoFromRemoteUrl("ssh://git@github.com/acme/widgets")).toBe(
|
|
432
|
+
"acme/widgets"
|
|
433
|
+
);
|
|
434
|
+
expect(parseRepoFromRemoteUrl("https://github.com/acme/widgets/\n")).toBe(
|
|
435
|
+
"acme/widgets"
|
|
436
|
+
);
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
it("returns undefined for unrecognized input", () => {
|
|
440
|
+
expect(parseRepoFromRemoteUrl("")).toBeUndefined();
|
|
441
|
+
expect(parseRepoFromRemoteUrl("not-a-url")).toBeUndefined();
|
|
442
|
+
});
|
|
443
|
+
});
|
|
444
|
+
|
|
445
|
+
function mount(slug: string, channels: DiscoveredChannel[]): AgentMountLike {
|
|
446
|
+
return {
|
|
447
|
+
slug,
|
|
448
|
+
project: { channels } as unknown as AgentProject,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
// Discovery erases the channel's state generic (`mod.default as
|
|
453
|
+
// ChannelDefinition`); mirror that here so githubChannel()'s typed state
|
|
454
|
+
// assigns to DiscoveredChannel.
|
|
455
|
+
function channel(id: string, definition: unknown): DiscoveredChannel {
|
|
456
|
+
return { id, definition: definition as DiscoveredChannel["definition"] };
|
|
457
|
+
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
import { isDevMode } from "../../internal/dev-mode.js";
|
|
11
11
|
import type {
|
|
12
12
|
AgentCloudOptions,
|
|
13
|
+
ArtifactsApi,
|
|
13
14
|
AuthContext,
|
|
14
15
|
ChannelDefinition,
|
|
15
16
|
ChannelEventHandlers,
|
|
@@ -68,7 +69,7 @@ import type {
|
|
|
68
69
|
*/
|
|
69
70
|
type GitHubParsedContext = Omit<
|
|
70
71
|
GitHubEventContext,
|
|
71
|
-
"github" | "thread" | "host"
|
|
72
|
+
"github" | "thread" | "host" | "artifacts"
|
|
72
73
|
>;
|
|
73
74
|
|
|
74
75
|
const DEFAULT_PULL_REQUEST_ACTIONS: ReadonlySet<string> = new Set([
|
|
@@ -737,6 +738,7 @@ async function handlePullRequest(input: {
|
|
|
737
738
|
bindingState: binding,
|
|
738
739
|
installationId,
|
|
739
740
|
host: input.args.host,
|
|
741
|
+
artifacts: input.args.artifacts,
|
|
740
742
|
});
|
|
741
743
|
const dispatch =
|
|
742
744
|
input.options.onPullRequest === undefined
|
|
@@ -828,6 +830,7 @@ async function handleIssueComment(input: {
|
|
|
828
830
|
bindingState: binding,
|
|
829
831
|
installationId,
|
|
830
832
|
host: input.args.host,
|
|
833
|
+
artifacts: input.args.artifacts,
|
|
831
834
|
});
|
|
832
835
|
|
|
833
836
|
const dispatch =
|
|
@@ -918,6 +921,7 @@ async function handleReviewComment(input: {
|
|
|
918
921
|
bindingState: binding,
|
|
919
922
|
installationId,
|
|
920
923
|
host: input.args.host,
|
|
924
|
+
artifacts: input.args.artifacts,
|
|
921
925
|
});
|
|
922
926
|
|
|
923
927
|
const dispatch =
|
|
@@ -994,6 +998,7 @@ async function handleIssue(input: {
|
|
|
994
998
|
bindingState: binding,
|
|
995
999
|
installationId,
|
|
996
1000
|
host: input.args.host,
|
|
1001
|
+
artifacts: input.args.artifacts,
|
|
997
1002
|
});
|
|
998
1003
|
const dispatch = await input.options.onIssue(ctx, parsed.issue);
|
|
999
1004
|
if (dispatch === null) {
|
|
@@ -1053,6 +1058,7 @@ async function handleCheckSuite(input: {
|
|
|
1053
1058
|
ctx: parsed.unanchoredCtx,
|
|
1054
1059
|
installationId,
|
|
1055
1060
|
host: input.args.host,
|
|
1061
|
+
artifacts: input.args.artifacts,
|
|
1056
1062
|
}),
|
|
1057
1063
|
parsed.checkSuite
|
|
1058
1064
|
);
|
|
@@ -1079,6 +1085,7 @@ async function handleCheckSuite(input: {
|
|
|
1079
1085
|
bindingState: binding,
|
|
1080
1086
|
installationId,
|
|
1081
1087
|
host: input.args.host,
|
|
1088
|
+
artifacts: input.args.artifacts,
|
|
1082
1089
|
});
|
|
1083
1090
|
const dispatch = await input.options.onCheckSuite(ctx, parsed.checkSuite);
|
|
1084
1091
|
if (dispatch === null) {
|
|
@@ -1143,6 +1150,7 @@ async function handleCheckRun(input: {
|
|
|
1143
1150
|
ctx: parsed.unanchoredCtx,
|
|
1144
1151
|
installationId,
|
|
1145
1152
|
host: input.args.host,
|
|
1153
|
+
artifacts: input.args.artifacts,
|
|
1146
1154
|
}),
|
|
1147
1155
|
parsed.checkRun
|
|
1148
1156
|
);
|
|
@@ -1169,6 +1177,7 @@ async function handleCheckRun(input: {
|
|
|
1169
1177
|
bindingState: binding,
|
|
1170
1178
|
installationId,
|
|
1171
1179
|
host: input.args.host,
|
|
1180
|
+
artifacts: input.args.artifacts,
|
|
1172
1181
|
});
|
|
1173
1182
|
const dispatch = await input.options.onCheckRun(ctx, parsed.checkRun);
|
|
1174
1183
|
if (dispatch === null) {
|
|
@@ -1229,6 +1238,7 @@ async function handleWorkflowRun(input: {
|
|
|
1229
1238
|
ctx: parsed.unanchoredCtx,
|
|
1230
1239
|
installationId,
|
|
1231
1240
|
host: input.args.host,
|
|
1241
|
+
artifacts: input.args.artifacts,
|
|
1232
1242
|
}),
|
|
1233
1243
|
parsed.workflowRun
|
|
1234
1244
|
);
|
|
@@ -1255,6 +1265,7 @@ async function handleWorkflowRun(input: {
|
|
|
1255
1265
|
bindingState: binding,
|
|
1256
1266
|
installationId,
|
|
1257
1267
|
host: input.args.host,
|
|
1268
|
+
artifacts: input.args.artifacts,
|
|
1258
1269
|
});
|
|
1259
1270
|
const dispatch = await input.options.onWorkflowRun(ctx, parsed.workflowRun);
|
|
1260
1271
|
if (dispatch === null) {
|
|
@@ -1326,6 +1337,7 @@ async function handleStatus(input: {
|
|
|
1326
1337
|
bindingState: binding,
|
|
1327
1338
|
installationId,
|
|
1328
1339
|
host: input.args.host,
|
|
1340
|
+
artifacts: input.args.artifacts,
|
|
1329
1341
|
});
|
|
1330
1342
|
const dispatch = await input.options.onStatus(ctx, parsed.status);
|
|
1331
1343
|
if (dispatch === null) {
|
|
@@ -1399,6 +1411,7 @@ async function handleGenericEvent(input: {
|
|
|
1399
1411
|
bindingState: binding,
|
|
1400
1412
|
installationId,
|
|
1401
1413
|
host: input.args.host,
|
|
1414
|
+
artifacts: input.args.artifacts,
|
|
1402
1415
|
});
|
|
1403
1416
|
const dispatch = await input.options.onEvent(ctx, parsed.event);
|
|
1404
1417
|
if (dispatch === null) {
|
|
@@ -2224,11 +2237,12 @@ function escapeRegExp(value: string): string {
|
|
|
2224
2237
|
*/
|
|
2225
2238
|
function enrichEventContext(input: {
|
|
2226
2239
|
bindingConfig: GitHubBindingConfig;
|
|
2227
|
-
ctx: Omit<GitHubEventContext, "github" | "thread" | "host"> &
|
|
2240
|
+
ctx: Omit<GitHubEventContext, "github" | "thread" | "host" | "artifacts"> &
|
|
2228
2241
|
Partial<Pick<GitHubEventContext, "github" | "thread">>;
|
|
2229
2242
|
bindingState: GitHubBindingState;
|
|
2230
2243
|
installationId?: number;
|
|
2231
2244
|
host: HostContext;
|
|
2245
|
+
artifacts: ArtifactsApi;
|
|
2232
2246
|
}): GitHubEventContext {
|
|
2233
2247
|
const { github, thread } = buildGitHubBinding({
|
|
2234
2248
|
config: input.bindingConfig,
|
|
@@ -2242,6 +2256,7 @@ function enrichEventContext(input: {
|
|
|
2242
2256
|
github,
|
|
2243
2257
|
thread,
|
|
2244
2258
|
host: input.host,
|
|
2259
|
+
artifacts: input.artifacts,
|
|
2245
2260
|
};
|
|
2246
2261
|
}
|
|
2247
2262
|
|
|
@@ -2254,6 +2269,7 @@ function enrichCiContext(input: {
|
|
|
2254
2269
|
ctx: GitHubParsedContext;
|
|
2255
2270
|
installationId?: number;
|
|
2256
2271
|
host: HostContext;
|
|
2272
|
+
artifacts: ArtifactsApi;
|
|
2257
2273
|
}): GitHubEventContext {
|
|
2258
2274
|
const binding = githubBindingStateForEvent({
|
|
2259
2275
|
kind: input.ctx.conversation.kind,
|
|
@@ -2269,6 +2285,7 @@ function enrichCiContext(input: {
|
|
|
2269
2285
|
bindingState: binding,
|
|
2270
2286
|
installationId: input.installationId,
|
|
2271
2287
|
host: input.host,
|
|
2288
|
+
artifacts: input.artifacts,
|
|
2272
2289
|
});
|
|
2273
2290
|
}
|
|
2274
2291
|
|