@cursor/july 0.1.45 → 0.1.46
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/AGENTS.md +4 -2
- package/README.md +3 -1
- package/dist/channels/github/api.d.ts +25 -0
- package/dist/channels/github/api.d.ts.map +1 -1
- package/dist/channels/github/api.js +48 -0
- package/dist/channels/github/cursor-account.d.ts +7 -0
- package/dist/channels/github/cursor-account.d.ts.map +1 -1
- package/dist/channels/github/cursor-account.js +18 -0
- package/dist/channels/github/defaults.d.ts +1 -1
- package/dist/channels/github/defaults.js +1 -1
- package/dist/channels/github/github-channel.d.ts.map +1 -1
- package/dist/channels/github/github-channel.js +2 -2
- package/dist/channels/github/progress.d.ts +6 -5
- package/dist/channels/github/progress.d.ts.map +1 -1
- package/dist/channels/github/progress.js +58 -16
- package/dist/channels/github/state.d.ts +11 -0
- package/dist/channels/github/state.d.ts.map +1 -1
- package/dist/channels/github/state.js +1 -1
- package/dist/channels/github/types.d.ts +3 -3
- package/dist/docs/404.html +2 -2
- package/dist/docs/ab.html +3 -3
- package/dist/docs/assets/{app.fHnNXEJ6.js → app.C2NFTb-h.js} +1 -1
- package/dist/docs/assets/chunks/@localSearchIndexroot.B-FxvuoI.js +1 -0
- package/dist/docs/assets/chunks/{VPLocalSearchBox.Cf6ghfFY.js → VPLocalSearchBox.o1_FJGip.js} +1 -1
- package/dist/docs/assets/chunks/{theme.B_5-VLe8.js → theme.BcGduE7g.js} +2 -2
- package/dist/docs/assets/{guides_github.md.DSg0j9qj.js → guides_github.md.B8QKtIpw.js} +7 -6
- package/dist/docs/assets/{guides_github.md.DSg0j9qj.lean.js → guides_github.md.B8QKtIpw.lean.js} +1 -1
- package/dist/docs/assets/{reference_agent-config.md.XRpxctsA.js → reference_agent-config.md.DHkkvcEZ.js} +4 -4
- package/dist/docs/assets/{reference_agent-config.md.XRpxctsA.lean.js → reference_agent-config.md.DHkkvcEZ.lean.js} +1 -1
- package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.js → reference_hooks.md.DJE5DXcT.js} +1 -1
- package/dist/docs/assets/{storage.md.DcGSnIxX.js → storage.md.CJYd5YmN.js} +2 -2
- package/dist/docs/assets/{storage.md.DcGSnIxX.lean.js → storage.md.CJYd5YmN.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/github.html +10 -9
- 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 +6 -6
- 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 +3 -3
- package/dist/docs/reference/hooks.html +5 -5
- 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 +5 -5
- package/dist/docs/templates/triage.html +3 -3
- package/dist/docs/troubleshooting.html +3 -3
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/internal/auth.d.ts +4 -0
- package/dist/internal/auth.d.ts.map +1 -1
- package/dist/internal/auth.js +7 -0
- package/dist/internal/durable-session-store.d.ts +35 -23
- package/dist/internal/durable-session-store.d.ts.map +1 -1
- package/dist/internal/durable-session-store.js +71 -26
- package/dist/internal/ledger-serving.d.ts +135 -67
- package/dist/internal/ledger-serving.d.ts.map +1 -1
- package/dist/internal/ledger-serving.js +326 -107
- package/dist/internal/server.d.ts.map +1 -1
- package/dist/internal/server.js +2 -2
- package/dist/internal/session-engine.d.ts +18 -17
- package/dist/internal/session-engine.d.ts.map +1 -1
- package/dist/internal/session-engine.js +60 -27
- package/dist/kv/memory-kv.d.ts +8 -0
- package/dist/kv/memory-kv.d.ts.map +1 -1
- package/dist/kv/memory-kv.js +8 -0
- package/dist/ledger/janitor.d.ts +60 -0
- package/dist/ledger/janitor.d.ts.map +1 -0
- package/dist/ledger/janitor.js +137 -0
- package/dist/ledger/session-ledger.d.ts +132 -43
- package/dist/ledger/session-ledger.d.ts.map +1 -1
- package/dist/ledger/session-ledger.js +282 -50
- package/dist/ledger.d.ts +9 -8
- package/dist/ledger.d.ts.map +1 -1
- package/dist/ledger.js +7 -7
- package/dist/playground/assets/{index-Dces8BDp.js → index-6wW6SAc4.js} +32 -32
- package/dist/playground/assets/index-CSRYDAFw.css +1 -0
- package/dist/playground/index.html +2 -2
- package/dist/storage-backends/cursor-hosted.d.ts +5 -0
- package/dist/storage-backends/cursor-hosted.d.ts.map +1 -1
- package/dist/storage-backends/cursor-hosted.js +6 -0
- package/dist/types.d.ts +19 -8
- package/dist/types.d.ts.map +1 -1
- package/docs/guides/github.md +23 -18
- package/docs/reference/agent-config.md +31 -36
- package/docs/reference/hooks.md +1 -1
- package/docs/storage.md +26 -9
- package/package.json +1 -1
- package/src/channels/github/api.ts +122 -0
- package/src/channels/github/cursor-account.ts +23 -0
- package/src/channels/github/defaults.ts +1 -1
- package/src/channels/github/github-channel.ts +5 -1
- package/src/channels/github/progress.ts +84 -19
- package/src/channels/github/state.ts +14 -0
- package/src/channels/github/types.ts +3 -3
- package/src/index.ts +1 -0
- package/src/internal/auth.ts +10 -0
- package/src/internal/durable-session-store.ts +66 -27
- package/src/internal/ledger-serving.ts +439 -148
- package/src/internal/server.ts +6 -2
- package/src/internal/session-engine.ts +95 -37
- package/src/kv/memory-kv.ts +8 -0
- package/src/ledger/janitor.ts +193 -0
- package/src/ledger/session-ledger.ts +372 -68
- package/src/ledger.ts +21 -6
- package/src/storage-backends/cursor-hosted.ts +17 -2
- package/src/types.ts +19 -8
- package/dist/docs/assets/chunks/@localSearchIndexroot.BMT0xM4I.js +0 -1
- package/dist/playground/assets/index-CZH9EFvO.css +0 -1
- /package/dist/docs/assets/{reference_hooks.md.4cbRRIIu.lean.js → reference_hooks.md.DJE5DXcT.lean.js} +0 -0
|
@@ -384,6 +384,128 @@ export async function createGitHubCommitStatus(
|
|
|
384
384
|
}
|
|
385
385
|
}
|
|
386
386
|
|
|
387
|
+
export type GitHubCheckRunStatus = "queued" | "in_progress" | "completed";
|
|
388
|
+
export type GitHubCheckRunConclusion =
|
|
389
|
+
| "action_required"
|
|
390
|
+
| "cancelled"
|
|
391
|
+
| "failure"
|
|
392
|
+
| "neutral"
|
|
393
|
+
| "skipped"
|
|
394
|
+
| "success"
|
|
395
|
+
| "timed_out";
|
|
396
|
+
|
|
397
|
+
export interface GitHubCheckRun {
|
|
398
|
+
htmlUrl: string | undefined;
|
|
399
|
+
id: number;
|
|
400
|
+
raw: unknown;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export interface GitHubCheckRunWriteInput {
|
|
404
|
+
conclusion?: GitHubCheckRunConclusion;
|
|
405
|
+
detailsUrl?: string;
|
|
406
|
+
name: string;
|
|
407
|
+
output?: { summary: string; title: string };
|
|
408
|
+
status: GitHubCheckRunStatus;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/** Create a check run (Checks API). */
|
|
412
|
+
export async function createGitHubCheckRun(
|
|
413
|
+
input: GitHubResourceInput &
|
|
414
|
+
GitHubCheckRunWriteInput & {
|
|
415
|
+
sha: string;
|
|
416
|
+
}
|
|
417
|
+
): Promise<GitHubCheckRun> {
|
|
418
|
+
const octokit = await createGitHubOctokit(input);
|
|
419
|
+
const now = new Date().toISOString();
|
|
420
|
+
try {
|
|
421
|
+
const response = await octokit.rest.checks.create({
|
|
422
|
+
owner: input.owner,
|
|
423
|
+
repo: input.repo,
|
|
424
|
+
name: input.name,
|
|
425
|
+
head_sha: input.sha,
|
|
426
|
+
status: input.status,
|
|
427
|
+
started_at: now,
|
|
428
|
+
...checkRunCompletionFields(input, now),
|
|
429
|
+
...checkRunDetailsUrl(input),
|
|
430
|
+
...checkRunOutput(input),
|
|
431
|
+
});
|
|
432
|
+
return toCheckRun(response.data);
|
|
433
|
+
} catch (error) {
|
|
434
|
+
throw wrapOctokitError(
|
|
435
|
+
error,
|
|
436
|
+
"POST",
|
|
437
|
+
`/repos/${input.owner}/${input.repo}/check-runs`
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Update a check run (Checks API). */
|
|
443
|
+
export async function updateGitHubCheckRun(
|
|
444
|
+
input: GitHubResourceInput &
|
|
445
|
+
GitHubCheckRunWriteInput & {
|
|
446
|
+
checkRunId: number;
|
|
447
|
+
}
|
|
448
|
+
): Promise<GitHubCheckRun> {
|
|
449
|
+
const octokit = await createGitHubOctokit(input);
|
|
450
|
+
try {
|
|
451
|
+
const response = await octokit.rest.checks.update({
|
|
452
|
+
owner: input.owner,
|
|
453
|
+
repo: input.repo,
|
|
454
|
+
check_run_id: input.checkRunId,
|
|
455
|
+
name: input.name,
|
|
456
|
+
status: input.status,
|
|
457
|
+
...checkRunCompletionFields(input, new Date().toISOString()),
|
|
458
|
+
...checkRunDetailsUrl(input),
|
|
459
|
+
...checkRunOutput(input),
|
|
460
|
+
});
|
|
461
|
+
return toCheckRun(response.data);
|
|
462
|
+
} catch (error) {
|
|
463
|
+
throw wrapOctokitError(
|
|
464
|
+
error,
|
|
465
|
+
"PATCH",
|
|
466
|
+
`/repos/${input.owner}/${input.repo}/check-runs/${input.checkRunId}`
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
function checkRunCompletionFields(
|
|
472
|
+
input: GitHubCheckRunWriteInput,
|
|
473
|
+
completedAt: string
|
|
474
|
+
): {
|
|
475
|
+
completed_at?: string;
|
|
476
|
+
conclusion?: GitHubCheckRunConclusion;
|
|
477
|
+
} {
|
|
478
|
+
if (input.status !== "completed" || input.conclusion === undefined) {
|
|
479
|
+
return {};
|
|
480
|
+
}
|
|
481
|
+
return { completed_at: completedAt, conclusion: input.conclusion };
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function checkRunDetailsUrl(input: GitHubCheckRunWriteInput): {
|
|
485
|
+
details_url?: string;
|
|
486
|
+
} {
|
|
487
|
+
return input.detailsUrl === undefined
|
|
488
|
+
? {}
|
|
489
|
+
: { details_url: input.detailsUrl };
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function checkRunOutput(input: GitHubCheckRunWriteInput): {
|
|
493
|
+
output?: { summary: string; title: string };
|
|
494
|
+
} {
|
|
495
|
+
return input.output === undefined ? {} : { output: input.output };
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function toCheckRun(raw: {
|
|
499
|
+
html_url?: string | null;
|
|
500
|
+
id?: number;
|
|
501
|
+
}): GitHubCheckRun {
|
|
502
|
+
return {
|
|
503
|
+
htmlUrl: typeof raw.html_url === "string" ? raw.html_url : undefined,
|
|
504
|
+
id: typeof raw.id === "number" ? raw.id : 0,
|
|
505
|
+
raw,
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
|
|
387
509
|
function toPostedComment(raw: {
|
|
388
510
|
html_url?: string;
|
|
389
511
|
id?: number;
|
|
@@ -117,6 +117,29 @@ export function parseCursorGithubRepoScope(
|
|
|
117
117
|
};
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Hosted `cursorAccount` mints `checks:write` on the `contents-write` tier.
|
|
122
|
+
* Enabling `progress.commitStatus` opts a `pr-write` (or omitted) channel up
|
|
123
|
+
* to that tier so github-proxy / `/github-token` can post Checks API runs.
|
|
124
|
+
* `"read"` cannot post merge-box checks.
|
|
125
|
+
*/
|
|
126
|
+
export function cursorGithubPermissionsForChannel(
|
|
127
|
+
options: Pick<GitHubChannelOptions, "progress">,
|
|
128
|
+
authored: CursorGithubPermissions | undefined = "pr-write"
|
|
129
|
+
): CursorGithubPermissions {
|
|
130
|
+
const base = authored ?? "pr-write";
|
|
131
|
+
const commitStatus = options.progress?.commitStatus;
|
|
132
|
+
if (commitStatus === undefined || commitStatus === false) {
|
|
133
|
+
return base;
|
|
134
|
+
}
|
|
135
|
+
if (base === "read") {
|
|
136
|
+
throw new Error(
|
|
137
|
+
'progress.commitStatus needs checks:write; cursorAccount permissions: "read" cannot post merge-box checks. Use "pr-write" or "contents-write".'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return "contents-write";
|
|
141
|
+
}
|
|
142
|
+
|
|
120
143
|
/** One host credential: the most privileged requested tier wins. */
|
|
121
144
|
export function resolveCursorGithubPermissions(
|
|
122
145
|
values: readonly CursorGithubPermissions[]
|
|
@@ -19,7 +19,7 @@ export interface BuildDefaultGitHubEventsOptions extends GitHubBindingConfig {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
|
-
* Built-in delivery: eyes reaction on turn start, optional
|
|
22
|
+
* Built-in delivery: eyes reaction on turn start, optional Checks API run /
|
|
23
23
|
* sticky banner via {@link GitHubProgressConfig}, post assistant text on
|
|
24
24
|
* `message.completed`, and a short error comment on turn/session failure.
|
|
25
25
|
*/
|
|
@@ -27,6 +27,7 @@ import {
|
|
|
27
27
|
import {
|
|
28
28
|
createCursorAccountGithubTokenResolver,
|
|
29
29
|
createPendingCursorGithubCredentials,
|
|
30
|
+
cursorGithubPermissionsForChannel,
|
|
30
31
|
type PendingCursorGithubCredentials,
|
|
31
32
|
parseCursorGithubRepoScope,
|
|
32
33
|
registerGithubCursorAccountRuntime,
|
|
@@ -608,7 +609,10 @@ export function githubChannel(
|
|
|
608
609
|
typeof options.cursorAccount === "object"
|
|
609
610
|
? parseCursorGithubRepoScope(
|
|
610
611
|
options.cursorAccount.repos,
|
|
611
|
-
|
|
612
|
+
cursorGithubPermissionsForChannel(
|
|
613
|
+
options,
|
|
614
|
+
options.cursorAccount.permissions
|
|
615
|
+
)
|
|
612
616
|
)
|
|
613
617
|
: undefined;
|
|
614
618
|
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Opt-in PR progress surfaces:
|
|
2
|
+
* Opt-in PR progress surfaces: Checks API run + sticky banner comment.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
|
-
|
|
6
|
+
createGitHubCheckRun,
|
|
7
7
|
createGitHubIssueComment,
|
|
8
8
|
GitHubApiError,
|
|
9
|
+
type GitHubCheckRunWriteInput,
|
|
10
|
+
updateGitHubCheckRun,
|
|
9
11
|
updateGitHubIssueComment,
|
|
10
12
|
} from "./api.js";
|
|
11
13
|
import type { GitHubBindingConfig } from "./binding.js";
|
|
12
14
|
import { formatGitHubOutboundComment } from "./binding.js";
|
|
13
15
|
import type { GitHubChannelState } from "./state.js";
|
|
14
16
|
|
|
15
|
-
/** GitHub
|
|
17
|
+
/** Merge-box check titles stay short; GitHub allows 1024, we clip to 140. */
|
|
16
18
|
const STATUS_DESCRIPTION_MAX = 140;
|
|
17
19
|
|
|
18
20
|
export type GitHubProgressPhase = "pending" | "success" | "failure";
|
|
@@ -24,7 +26,7 @@ export interface GitHubProgressPhaseCopy {
|
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export interface GitHubCommitStatusProgress extends GitHubProgressPhaseCopy {
|
|
27
|
-
/**
|
|
29
|
+
/** Check run name (unique per check in the merge box). */
|
|
28
30
|
context: string;
|
|
29
31
|
}
|
|
30
32
|
|
|
@@ -34,9 +36,10 @@ export interface GitHubProgressConfig {
|
|
|
34
36
|
/** When false, skip the eyes reaction on turn.started. Defaults to true. */
|
|
35
37
|
reactions?: boolean;
|
|
36
38
|
/**
|
|
37
|
-
* Opt-in
|
|
38
|
-
* behavior (no
|
|
39
|
-
*
|
|
39
|
+
* Opt-in Checks API run on the PR head. Omit or `false` keeps today's
|
|
40
|
+
* behavior (no check from default events). With `cursorAccount`, enabling
|
|
41
|
+
* this opts the minted token up to `permissions: "contents-write"` so the
|
|
42
|
+
* hosted github-proxy lease includes `checks:write`.
|
|
40
43
|
*/
|
|
41
44
|
commitStatus?: false | GitHubCommitStatusProgress;
|
|
42
45
|
/**
|
|
@@ -127,7 +130,7 @@ export async function applyGitHubProgress(input: {
|
|
|
127
130
|
const tasks: Array<Promise<void>> = [];
|
|
128
131
|
if (commitStatus !== null) {
|
|
129
132
|
tasks.push(
|
|
130
|
-
|
|
133
|
+
postProgressCheckRun({
|
|
131
134
|
phase: input.phase,
|
|
132
135
|
channel: input.channel,
|
|
133
136
|
options: input.options,
|
|
@@ -149,7 +152,7 @@ export async function applyGitHubProgress(input: {
|
|
|
149
152
|
await Promise.all(tasks);
|
|
150
153
|
}
|
|
151
154
|
|
|
152
|
-
async function
|
|
155
|
+
async function postProgressCheckRun(input: {
|
|
153
156
|
phase: GitHubProgressPhase;
|
|
154
157
|
channel: { state: GitHubChannelState };
|
|
155
158
|
options: GitHubProgressOptions;
|
|
@@ -164,6 +167,7 @@ async function postCommitStatus(input: {
|
|
|
164
167
|
) {
|
|
165
168
|
return;
|
|
166
169
|
}
|
|
170
|
+
const sha = state.headSha;
|
|
167
171
|
const description = clipStatusDescription(
|
|
168
172
|
progressPhaseCopy({
|
|
169
173
|
copy: input.commitStatus,
|
|
@@ -171,23 +175,84 @@ async function postCommitStatus(input: {
|
|
|
171
175
|
label: progressLabel(input.options, input.commitStatus.context),
|
|
172
176
|
})
|
|
173
177
|
);
|
|
178
|
+
const write: GitHubCheckRunWriteInput = {
|
|
179
|
+
name: input.commitStatus.context,
|
|
180
|
+
...checkRunPhaseFields(input.phase),
|
|
181
|
+
output: { title: description, summary: description },
|
|
182
|
+
};
|
|
183
|
+
const resource = {
|
|
184
|
+
api: input.options.api,
|
|
185
|
+
credentials: input.options.credentials,
|
|
186
|
+
installationId: state.installationId ?? undefined,
|
|
187
|
+
owner: state.owner,
|
|
188
|
+
repo: state.repo,
|
|
189
|
+
};
|
|
190
|
+
const reuseId =
|
|
191
|
+
state.progressCheckRunId !== null && state.progressCheckRunSha === sha
|
|
192
|
+
? state.progressCheckRunId
|
|
193
|
+
: null;
|
|
194
|
+
// Completed runs cannot go back to in_progress. GitHub often 200s and
|
|
195
|
+
// leaves the run completed (sometimes 422). Create a new run so a later
|
|
196
|
+
// turn on the same SHA shows pending again.
|
|
197
|
+
const completedBlocksReuse =
|
|
198
|
+
write.status === "in_progress" && state.progressCheckRunCompleted === true;
|
|
174
199
|
try {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
200
|
+
if (reuseId !== null && !completedBlocksReuse) {
|
|
201
|
+
try {
|
|
202
|
+
await updateGitHubCheckRun({
|
|
203
|
+
...resource,
|
|
204
|
+
...write,
|
|
205
|
+
checkRunId: reuseId,
|
|
206
|
+
});
|
|
207
|
+
state.progressCheckRunCompleted = write.status === "completed";
|
|
208
|
+
return;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (!shouldRecreateCheckRun(error, write.status)) {
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
state.progressCheckRunId = null;
|
|
214
|
+
state.progressCheckRunSha = null;
|
|
215
|
+
state.progressCheckRunCompleted = false;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const created = await createGitHubCheckRun({
|
|
219
|
+
...resource,
|
|
220
|
+
...write,
|
|
221
|
+
sha,
|
|
185
222
|
});
|
|
223
|
+
state.progressCheckRunId = created.id;
|
|
224
|
+
state.progressCheckRunSha = sha;
|
|
225
|
+
state.progressCheckRunCompleted = write.status === "completed";
|
|
186
226
|
} catch {
|
|
187
227
|
// Soft-fail: missing token / permission is fine.
|
|
188
228
|
}
|
|
189
229
|
}
|
|
190
230
|
|
|
231
|
+
function shouldRecreateCheckRun(
|
|
232
|
+
error: unknown,
|
|
233
|
+
status: GitHubCheckRunWriteInput["status"]
|
|
234
|
+
): boolean {
|
|
235
|
+
if (!(error instanceof GitHubApiError)) {
|
|
236
|
+
return false;
|
|
237
|
+
}
|
|
238
|
+
if (error.status === 404) {
|
|
239
|
+
return true;
|
|
240
|
+
}
|
|
241
|
+
return error.status === 422 && status === "in_progress";
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
function checkRunPhaseFields(
|
|
245
|
+
phase: GitHubProgressPhase
|
|
246
|
+
): Pick<GitHubCheckRunWriteInput, "conclusion" | "status"> {
|
|
247
|
+
if (phase === "pending") {
|
|
248
|
+
return { status: "in_progress" };
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
status: "completed",
|
|
252
|
+
conclusion: phase === "success" ? "success" : "failure",
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
|
|
191
256
|
async function upsertBanner(input: {
|
|
192
257
|
phase: GitHubProgressPhase;
|
|
193
258
|
channel: { state: GitHubChannelState };
|
|
@@ -13,6 +13,17 @@ export interface GitHubChannelState extends GitHubBindingState {
|
|
|
13
13
|
headRef: string | null;
|
|
14
14
|
/** Sticky progress banner comment id (issue/PR timeline). */
|
|
15
15
|
progressBannerCommentId: number | null;
|
|
16
|
+
/** Latest Checks API run id for `progress.commitStatus`. */
|
|
17
|
+
progressCheckRunId: number | null;
|
|
18
|
+
/** Head SHA the latest check run was created against. */
|
|
19
|
+
progressCheckRunSha: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Whether that run is already `completed`. `null` means unknown (wake
|
|
22
|
+
* patches omit this so merge keeps the persisted value). GitHub rejects
|
|
23
|
+
* PATCH back to `in_progress`, so a later pending turn on the same SHA
|
|
24
|
+
* must create a new run.
|
|
25
|
+
*/
|
|
26
|
+
progressCheckRunCompleted: boolean | null;
|
|
16
27
|
}
|
|
17
28
|
|
|
18
29
|
export function emptyGitHubChannelState(
|
|
@@ -35,6 +46,9 @@ export function emptyGitHubChannelState(
|
|
|
35
46
|
baseRef: null,
|
|
36
47
|
headRef: null,
|
|
37
48
|
progressBannerCommentId: null,
|
|
49
|
+
progressCheckRunId: null,
|
|
50
|
+
progressCheckRunSha: null,
|
|
51
|
+
progressCheckRunCompleted: null,
|
|
38
52
|
...overrides,
|
|
39
53
|
};
|
|
40
54
|
}
|
|
@@ -452,7 +452,7 @@ export interface GitHubChannelOptions {
|
|
|
452
452
|
* Token capability tier:
|
|
453
453
|
* - `"read"` — PR/issue/status read
|
|
454
454
|
* - `"pr-write"` (default) — comments / PR+issue write, no `contents:write`
|
|
455
|
-
* - `"contents-write"` — push code and
|
|
455
|
+
* - `"contents-write"` — push code and post merge-box check runs (host-held)
|
|
456
456
|
*/
|
|
457
457
|
permissions?: "read" | "pr-write" | "contents-write";
|
|
458
458
|
};
|
|
@@ -504,12 +504,12 @@ export interface GitHubChannelOptions {
|
|
|
504
504
|
onEvent?: GitHubEventHook;
|
|
505
505
|
/**
|
|
506
506
|
* Override / extend default stream-event handlers (eyes reaction, reply
|
|
507
|
-
* posting, opt-in
|
|
507
|
+
* posting, opt-in check run / banner). Handlers you provide replace
|
|
508
508
|
* the matching defaults.
|
|
509
509
|
*/
|
|
510
510
|
events?: ChannelEventHandlers<GitHubChannelState>;
|
|
511
511
|
/**
|
|
512
|
-
* Acknowledgement progress surfaces. Reactions default on;
|
|
512
|
+
* Acknowledgement progress surfaces. Reactions default on; merge-box check
|
|
513
513
|
* and sticky banner are opt-in via `commitStatus` / `banner`.
|
|
514
514
|
*/
|
|
515
515
|
progress?: GitHubProgressConfig;
|
package/src/index.ts
CHANGED
package/src/internal/auth.ts
CHANGED
|
@@ -20,6 +20,16 @@ export function isSafeSessionId(sessionId: string): boolean {
|
|
|
20
20
|
);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
|
+
/** Thrown when a send addresses a session owned by a different principal. */
|
|
24
|
+
export class SessionAuthError extends Error {
|
|
25
|
+
constructor(sessionId: string) {
|
|
26
|
+
super(
|
|
27
|
+
`Session ${sessionId} belongs to a different principal; pass the original caller auth on follow-up`
|
|
28
|
+
);
|
|
29
|
+
this.name = "SessionAuthError";
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
/** True when both sides identify the same principal (or both are null). */
|
|
24
34
|
export function samePrincipal(
|
|
25
35
|
a: AuthContext | null,
|
|
@@ -3,11 +3,13 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Two APIs, one backend: `defineStorage` is the **storage API** (agent
|
|
5
5
|
* state — session records, event chunks, author KV), and the session
|
|
6
|
-
* ledger is the **durable session API** (session identity,
|
|
7
|
-
* fenced commits). The ledger consumes {@link SessionLedgerKv} —
|
|
8
|
-
* the
|
|
9
|
-
*
|
|
10
|
-
*
|
|
6
|
+
* ledger is the **durable session API** (session identity, mailboxes,
|
|
7
|
+
* leases, fenced commits). The ledger consumes {@link SessionLedgerKv} —
|
|
8
|
+
* the full CAS-capable KV surface, since the inbox reads/sweeps mailbox
|
|
9
|
+
* payload blobs (`get`/`put`/`delete`) and scans by prefix (`listKeys`)
|
|
10
|
+
* on top of the CAS trio; this module adapts a storage backend's methods
|
|
11
|
+
* into that interface, so a deployment's one authored backend serves
|
|
12
|
+
* both APIs by default.
|
|
11
13
|
*
|
|
12
14
|
* Calls go straight at the config's methods — NOT through the
|
|
13
15
|
* StorageCoordinator: the coordinator's queue is bounded, serialized, and
|
|
@@ -22,6 +24,7 @@ import {
|
|
|
22
24
|
type StorageConfig,
|
|
23
25
|
type StorageContext,
|
|
24
26
|
} from "../storage.js";
|
|
27
|
+
import type { JsonValue } from "../types.js";
|
|
25
28
|
|
|
26
29
|
export interface StorageCasKvArgs {
|
|
27
30
|
agentName: string;
|
|
@@ -29,10 +32,29 @@ export interface StorageCasKvArgs {
|
|
|
29
32
|
}
|
|
30
33
|
|
|
31
34
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
35
|
+
* The one place the ledger's physical namespace is minted: the per-agent
|
|
36
|
+
* prefix for ledger state, following the {@link storageKeys} convention
|
|
37
|
+
* (`<root>/<agent>/<family>/…`) so a store shared by several ledger-mode
|
|
38
|
+
* agents (one fileKv dir, one hosted deployment) never mixes their
|
|
39
|
+
* sessions, leases, or mailboxes — and so each agent's janitor
|
|
40
|
+
* prefix-scan only ever sees its own ledgers. The extra
|
|
41
|
+
* `durable-session` segment keeps the ledger's `session/` family out of
|
|
42
|
+
* `storageKeys.sessionPrefix` scans (storage restore lists
|
|
43
|
+
* `<root>/<agent>/session/`). The ledger itself never sees this string:
|
|
44
|
+
* it speaks bare `session/…` / `ledger/…` / `mailbox/…` keys against a
|
|
45
|
+
* handle that {@link namespacedKv} has already scoped.
|
|
46
|
+
*/
|
|
47
|
+
export function ledgerKeyPrefix(agentName: string): string {
|
|
48
|
+
return `${STORAGE_KEY_ROOT}/${keySegment(agentName)}/durable-session/`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Adapt a storage backend into the ledger's KV interface. Throws when
|
|
53
|
+
* the backend does not implement the `cas` group, or omits `get` /
|
|
54
|
+
* `delete` (optional for resident-only sinks, but the ledger reads and
|
|
55
|
+
* sweeps mailbox payload blobs through the same store) — the caller
|
|
56
|
+
* turns that into the fail-fast startup error for `serving: "ledger"`.
|
|
57
|
+
* Every call carries a `reason: "coordination"` {@link StorageContext}.
|
|
36
58
|
*/
|
|
37
59
|
export function storageCasKv(
|
|
38
60
|
config: StorageConfig,
|
|
@@ -49,7 +71,23 @@ export function storageCasKv(
|
|
|
49
71
|
"storageCasKv: the storage backend does not implement the cas group (conditional writes)"
|
|
50
72
|
);
|
|
51
73
|
}
|
|
74
|
+
const get = config.get;
|
|
75
|
+
const remove = config.delete;
|
|
76
|
+
if (get === undefined || remove === undefined) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
"storageCasKv: the storage backend must implement get and delete (the session ledger reads and sweeps payload blobs through the same store)"
|
|
79
|
+
);
|
|
80
|
+
}
|
|
52
81
|
return {
|
|
82
|
+
async get(key): Promise<JsonValue | undefined> {
|
|
83
|
+
return (await get(key, ctx)) ?? undefined;
|
|
84
|
+
},
|
|
85
|
+
async put(key, value): Promise<void> {
|
|
86
|
+
await config.put(key, value, ctx);
|
|
87
|
+
},
|
|
88
|
+
async delete(key): Promise<void> {
|
|
89
|
+
await remove(key, ctx);
|
|
90
|
+
},
|
|
53
91
|
async getWithVersion(key) {
|
|
54
92
|
return await cas.getWithVersion(key, ctx);
|
|
55
93
|
},
|
|
@@ -59,37 +97,38 @@ export function storageCasKv(
|
|
|
59
97
|
async putIfVersion(key, value, expectedVersion) {
|
|
60
98
|
return await cas.putIfVersion(key, value, expectedVersion, ctx);
|
|
61
99
|
},
|
|
100
|
+
async listKeys(prefix) {
|
|
101
|
+
return await cas.listKeys(prefix, ctx);
|
|
102
|
+
},
|
|
62
103
|
};
|
|
63
104
|
}
|
|
64
105
|
|
|
65
106
|
/**
|
|
66
|
-
*
|
|
67
|
-
* `
|
|
68
|
-
*
|
|
69
|
-
*
|
|
70
|
-
*
|
|
71
|
-
*
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
/**
|
|
78
|
-
* Scope a raw store handle under a namespace, prepending it to every
|
|
79
|
-
* key on the way in. Applied exactly once, where the engine constructs
|
|
80
|
-
* the ledger's store handle — the same conceptual boundary where
|
|
81
|
-
* `host.kv` gets its agent scoping. Internal: not part of the public kv
|
|
82
|
-
* interface, and callers (including the explicit `durableSessionStore`
|
|
83
|
-
* override) always supply the raw store — they never write prefixes.
|
|
107
|
+
* Scope a raw store handle under a namespace: every key going in gets it
|
|
108
|
+
* prepended, and `listKeys` results come back with it **stripped**, so a
|
|
109
|
+
* caller that lists and then reads/deletes the returned keys stays fully
|
|
110
|
+
* prefix-free (the janitor scan and the orphan sweep do exactly that).
|
|
111
|
+
* Applied exactly once, where the engine constructs the ledger's store
|
|
112
|
+
* handle — the same conceptual boundary where `host.kv` gets its agent
|
|
113
|
+
* scoping. Internal: not part of the public kv interface, and callers
|
|
114
|
+
* (including the explicit `durableSessionStore` override) always supply
|
|
115
|
+
* the raw store — they never write prefixes.
|
|
84
116
|
*/
|
|
85
117
|
export function namespacedKv(
|
|
86
118
|
store: SessionLedgerKv,
|
|
87
119
|
namespace: string
|
|
88
120
|
): SessionLedgerKv {
|
|
89
121
|
return {
|
|
122
|
+
get: (key) => store.get(namespace + key),
|
|
123
|
+
put: (key, value) => store.put(namespace + key, value),
|
|
124
|
+
delete: (key) => store.delete(namespace + key),
|
|
90
125
|
getWithVersion: (key) => store.getWithVersion(namespace + key),
|
|
91
126
|
putIfAbsent: (key, value) => store.putIfAbsent(namespace + key, value),
|
|
92
127
|
putIfVersion: (key, value, expectedVersion) =>
|
|
93
128
|
store.putIfVersion(namespace + key, value, expectedVersion),
|
|
129
|
+
listKeys: async (prefix) => {
|
|
130
|
+
const keys = await store.listKeys(namespace + prefix);
|
|
131
|
+
return keys.map((key) => key.slice(namespace.length));
|
|
132
|
+
},
|
|
94
133
|
};
|
|
95
134
|
}
|