@midhunxavier/code-flow 0.0.0-stage → 0.1.0-preview.3
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/LICENSE +21 -0
- package/README.md +141 -2
- package/bin/codeflow.mjs +8 -0
- package/bin/launcher.mjs +127 -0
- package/dist/remote-host.json +1 -0
- package/licenses/inter.txt +92 -0
- package/licenses/preact.txt +21 -0
- package/licenses/signals-core.txt +21 -0
- package/licenses/signals.txt +21 -0
- package/licenses/typescript-notices.txt +193 -0
- package/licenses/typescript.txt +55 -0
- package/licenses/uqr.txt +22 -0
- package/licenses/xterm-fit.txt +19 -0
- package/licenses/xterm.txt +21 -0
- package/package.json +34 -4
- package/public/assets/changes-62ZIEQSN.js +1 -0
- package/public/assets/chunk-2PZVOKID.js +1 -0
- package/public/assets/chunk-4D5IENEB.js +1 -0
- package/public/assets/chunk-5SARP5MU.js +1 -0
- package/public/assets/chunk-63YLZ3PR.js +1 -0
- package/public/assets/chunk-7FUHKF2M.js +1 -0
- package/public/assets/chunk-APQEVJ2K.js +1 -0
- package/public/assets/chunk-CGOZQMEZ.js +2 -0
- package/public/assets/chunk-J4VYS3KV.js +2 -0
- package/public/assets/chunk-L7TUCIYF.js +1 -0
- package/public/assets/chunk-SIHDWM2Y.js +154 -0
- package/public/assets/chunk-TEJVYIKC.js +1 -0
- package/public/assets/code-browser-VB2HGZSG.js +4 -0
- package/public/assets/filtered-tasks-ES5CP4KV.js +1 -0
- package/public/assets/interactive-browser-7F5ZXRUV.js +7 -0
- package/public/assets/join-project-6RTFHNCA.js +1 -0
- package/public/assets/main-65EW73SB.js +1 -0
- package/public/assets/project-invitations-view-GMPWGWYM.js +1 -0
- package/public/assets/settings-UB5Q4AQM.js +9 -0
- package/public/assets/styles-18792BB4.css +32 -0
- package/public/assets/team-server-wizard-2GLDWS6D.js +8 -0
- package/public/assets/terminal-IWAIM4DE.js +62 -0
- package/public/assets.json +1 -0
- package/public/bundle-meta.json +1 -0
- package/public/docs/.nojekyll +0 -0
- package/public/docs/accessibility.html +19 -0
- package/public/docs/acp-agents.html +117 -0
- package/public/docs/agent-containers.html +79 -0
- package/public/docs/api.html +1130 -0
- package/public/docs/app.js +342 -0
- package/public/docs/application-improvements.html +275 -0
- package/public/docs/architecture-explorer.html +19 -0
- package/public/docs/architecture.html +149 -0
- package/public/docs/auto-team.html +148 -0
- package/public/docs/automations.html +109 -0
- package/public/docs/checkpoint-review.html +37 -0
- package/public/docs/code-hosts.html +35 -0
- package/public/docs/code-intelligence.html +35 -0
- package/public/docs/configuration.html +346 -0
- package/public/docs/context-inspection.html +23 -0
- package/public/docs/coordinated-changes.html +28 -0
- package/public/docs/deferred-runs.html +25 -0
- package/public/docs/dependency-discovery.html +19 -0
- package/public/docs/documentation.html +84 -0
- package/public/docs/dynamic-teams-evaluation.html +174 -0
- package/public/docs/dynamic-teams-implementation-plan.html +882 -0
- package/public/docs/dynamic-teams-implementation-review-1.html +141 -0
- package/public/docs/dynamic-teams-plan-review-0.html +204 -0
- package/public/docs/extensions.html +42 -0
- package/public/docs/external-sources.html +96 -0
- package/public/docs/filesystem-policy.html +26 -0
- package/public/docs/filtered-tasks.html +44 -0
- package/public/docs/font-license.txt +92 -0
- package/public/docs/getting-started.html +143 -0
- package/public/docs/github-review-threads.html +26 -0
- package/public/docs/github.html +157 -0
- package/public/docs/help.html +69 -0
- package/public/docs/index.html +88 -0
- package/public/docs/installation.html +80 -0
- package/public/docs/launch-profiles.html +48 -0
- package/public/docs/maintenance.html +60 -0
- package/public/docs/memory-claims.html +31 -0
- package/public/docs/memory-storage.html +58 -0
- package/public/docs/notifications.html +39 -0
- package/public/docs/patterns.html +195 -0
- package/public/docs/performance-results.html +210 -0
- package/public/docs/performance.html +166 -0
- package/public/docs/project-conventions.html +22 -0
- package/public/docs/project-first-navigation-plan.html +587 -0
- package/public/docs/project-teams-implementation-plan.html +772 -0
- package/public/docs/project-teams.html +129 -0
- package/public/docs/provider-compatibility.html +20 -0
- package/public/docs/releasing.html +65 -0
- package/public/docs/remote-workers.html +64 -0
- package/public/docs/review-inbox.html +26 -0
- package/public/docs/run-storage.html +23 -0
- package/public/docs/runs.html +141 -0
- package/public/docs/search.json +1 -0
- package/public/docs/security.html +133 -0
- package/public/docs/shared-projects.html +31 -0
- package/public/docs/ssh-projects.html +60 -0
- package/public/docs/style.css +768 -0
- package/public/docs/task-reruns.html +30 -0
- package/public/docs/team-collaboration.html +412 -0
- package/public/docs/verification.html +22 -0
- package/public/docs/vscode.html +53 -0
- package/public/docs/worker-permission-verification.html +131 -0
- package/public/index.html +17 -0
- package/scripts/check-node.mjs +7 -0
- package/scripts/codeflow.mjs +55 -0
- package/scripts/computer-use.swift +64 -0
- package/scripts/pty.py +273 -0
- package/src/acp-connections.js +110 -0
- package/src/adapters/acp-presets.js +37 -0
- package/src/adapters/acp-types.js +0 -0
- package/src/adapters/acp.js +328 -0
- package/src/adapters/agent-tools.js +28 -0
- package/src/adapters/agy-auth.js +67 -0
- package/src/adapters/agy-isolation.js +242 -0
- package/src/adapters/agy-tools.js +45 -0
- package/src/adapters/agy.js +326 -0
- package/src/adapters/claude-stream.js +58 -0
- package/src/adapters/claude.js +282 -0
- package/src/adapters/codex-app-server.js +555 -0
- package/src/adapters/codex.js +311 -0
- package/src/adapters/errors.js +57 -0
- package/src/adapters/index.js +91 -0
- package/src/adapters/managed-permissions.js +37 -0
- package/src/adapters/model-discovery.js +100 -0
- package/src/adapters/process.js +433 -0
- package/src/adapters/team-tools.js +103 -0
- package/src/adapters/types.js +0 -0
- package/src/agent-browser.js +688 -0
- package/src/agent-computer.js +88 -0
- package/src/agent-container.js +155 -0
- package/src/agent-team-definitions.js +131 -0
- package/src/agent-tool-client.mjs +79 -0
- package/src/agent-tools.js +296 -0
- package/src/approvals.js +134 -0
- package/src/architecture.js +456 -0
- package/src/assets.js +44 -0
- package/src/automation.js +901 -0
- package/src/browser-files.js +293 -0
- package/src/browser-ownership.js +150 -0
- package/src/certificate-renewal.js +75 -0
- package/src/chat-family.js +25 -0
- package/src/chat-handoff.js +87 -0
- package/src/checkpoint-review.js +72 -0
- package/src/checks.js +303 -0
- package/src/code-host-git.js +102 -0
- package/src/code-hosts-api.js +99 -0
- package/src/code-hosts.js +482 -0
- package/src/code-intelligence-worker.mjs +452 -0
- package/src/code-intelligence.js +141 -0
- package/src/collab/api.js +365 -0
- package/src/collab/checkpoints.js +198 -0
- package/src/collab/connections.js +139 -0
- package/src/collab/host-execution.js +347 -0
- package/src/collab/hub-client.js +210 -0
- package/src/collab/link-api.js +229 -0
- package/src/collab/link.js +595 -0
- package/src/collab/model.js +496 -0
- package/src/collab/runner.js +1014 -0
- package/src/collab/store.js +1452 -0
- package/src/collab/terminals.js +361 -0
- package/src/collaboration-reviews.js +116 -0
- package/src/config.js +48 -0
- package/src/context-budget.js +14 -0
- package/src/context-inspection.js +204 -0
- package/src/context.js +154 -0
- package/src/conventions.js +283 -0
- package/src/coordinated-api.js +247 -0
- package/src/deferred-launch.js +82 -0
- package/src/deferred-runs.js +515 -0
- package/src/dependency-health.js +35 -0
- package/src/diff.js +43 -0
- package/src/docs-help.js +316 -0
- package/src/doctor.js +232 -0
- package/src/documentation-changes.js +20 -0
- package/src/engine.js +5258 -0
- package/src/env.js +44 -0
- package/src/execution-budget.js +8 -0
- package/src/experience.js +50 -0
- package/src/extensions.js +144 -0
- package/src/filesystem-access.js +107 -0
- package/src/filesystem-policy.js +238 -0
- package/src/filtered-task-limits.js +93 -0
- package/src/filtered-tasks-api.js +53 -0
- package/src/filtered-tasks.js +576 -0
- package/src/frame-policy.js +66 -0
- package/src/fs.js +57 -0
- package/src/git.js +626 -0
- package/src/github-review-threads.js +181 -0
- package/src/github.js +538 -0
- package/src/http.js +69 -0
- package/src/improve-settings.js +38 -0
- package/src/improve.js +187 -0
- package/src/integrations.js +373 -0
- package/src/integrity.js +259 -0
- package/src/journey-api.js +77 -0
- package/src/journey-replay.js +65 -0
- package/src/journey-store.js +97 -0
- package/src/journeys.js +60 -0
- package/src/json.js +81 -0
- package/src/launch-profiles.js +292 -0
- package/src/learnings.js +80 -0
- package/src/local-app-url.js +19 -0
- package/src/log.js +39 -0
- package/src/maintenance.js +335 -0
- package/src/memory-analysis.js +182 -0
- package/src/memory-claims.js +153 -0
- package/src/memory-sqlite.js +236 -0
- package/src/memory.js +552 -0
- package/src/merge-validation.js +58 -0
- package/src/model-catalog.js +239 -0
- package/src/model-research.js +91 -0
- package/src/notes.js +176 -0
- package/src/notifications.js +413 -0
- package/src/patterns/auto-team-model.js +230 -0
- package/src/patterns/auto-team.js +816 -0
- package/src/patterns/council.js +199 -0
- package/src/patterns/debate.js +188 -0
- package/src/patterns/findings.js +141 -0
- package/src/patterns/index.js +21 -0
- package/src/patterns/loop.js +158 -0
- package/src/patterns/pipeline.js +125 -0
- package/src/patterns/prompts.js +181 -0
- package/src/patterns/race.js +147 -0
- package/src/patterns/schemas.js +40 -0
- package/src/patterns/solo.js +46 -0
- package/src/patterns/supervisor.js +235 -0
- package/src/patterns/task-draft.js +29 -0
- package/src/patterns/task-review.js +45 -0
- package/src/patterns/tdd.js +231 -0
- package/src/patterns/team-strategies.js +67 -0
- package/src/performance.js +43 -0
- package/src/persistence-status.js +35 -0
- package/src/phone-sessions.js +96 -0
- package/src/preview-overlay.js +392 -0
- package/src/preview.js +230 -0
- package/src/privacy.js +23 -0
- package/src/project-report.js +22 -0
- package/src/project-team-budget.js +22 -0
- package/src/project-team-model.js +225 -0
- package/src/project-team-policy.js +42 -0
- package/src/project-team-store.js +234 -0
- package/src/project-team-usage.js +24 -0
- package/src/project-teams-api.js +43 -0
- package/src/project-teams.js +644 -0
- package/src/projects.js +290 -0
- package/src/provider-eligibility.js +9 -0
- package/src/readiness.js +98 -0
- package/src/remote-tls.js +66 -0
- package/src/remote-workers/bundles.js +198 -0
- package/src/remote-workers/client.js +332 -0
- package/src/remote-workers/durability.js +37 -0
- package/src/remote-workers/protocol.js +68 -0
- package/src/remote-workers/stdio.js +138 -0
- package/src/remote-workers/worker.js +304 -0
- package/src/remote.js +204 -0
- package/src/repo-config.js +201 -0
- package/src/repo-coordination.js +725 -0
- package/src/repo-tree.js +106 -0
- package/src/repository-identity.js +13 -0
- package/src/retention-policy.js +466 -0
- package/src/review-inbox.js +38 -0
- package/src/routing.js +49 -0
- package/src/run-analysis.js +111 -0
- package/src/run-cold-store.js +231 -0
- package/src/run-environment.js +15 -0
- package/src/run-history.js +91 -0
- package/src/run-log.js +160 -0
- package/src/run-permissions.js +111 -0
- package/src/run-projection.js +77 -0
- package/src/run-recovery.js +53 -0
- package/src/run-status.js +56 -0
- package/src/serve.js +138 -0
- package/src/server.js +2871 -0
- package/src/shared-access.js +103 -0
- package/src/shared-accounts.js +381 -0
- package/src/shared-api.js +239 -0
- package/src/shell.js +346 -0
- package/src/ship.js +344 -0
- package/src/slots.js +74 -0
- package/src/snapshot.js +388 -0
- package/src/source-context.js +17 -0
- package/src/sse-writer.js +70 -0
- package/src/ssh-projects/bootstrap.mjs +95 -0
- package/src/ssh-projects/config.js +45 -0
- package/src/ssh-projects/gateway.js +143 -0
- package/src/ssh-projects/manager.js +320 -0
- package/src/ssh-projects/team-autostart.mjs +93 -0
- package/src/ssh-projects/team-remove.mjs +69 -0
- package/src/ssh-projects/team-status.mjs +39 -0
- package/src/ssh-projects/transport.js +133 -0
- package/src/stats.js +139 -0
- package/src/store.js +544 -0
- package/src/stream.js +91 -0
- package/src/sweeps.js +241 -0
- package/src/tailscale.js +101 -0
- package/src/task-draft.js +23 -0
- package/src/task-team.js +164 -0
- package/src/team-evaluation.js +243 -0
- package/src/team-mailbox.js +160 -0
- package/src/team-memory.js +84 -0
- package/src/team-rerun-graph.js +15 -0
- package/src/team-reruns.js +130 -0
- package/src/team-scheduler.js +121 -0
- package/src/team-steering.js +21 -0
- package/src/team-template-store.js +98 -0
- package/src/team-templates.js +117 -0
- package/src/team-tool-client.mjs +79 -0
- package/src/team-tools.js +235 -0
- package/src/teams.js +0 -0
- package/src/templates.js +62 -0
- package/src/transcript.js +134 -0
- package/src/verification-integrity.js +27 -0
- package/src/verification.js +183 -0
- package/src/watch.js +195 -0
- package/src/work-queue.js +47 -0
- package/src/workflow-control.js +83 -0
- package/src/workspace-api.js +207 -0
- package/src/workspace-changes.js +61 -0
- package/src/workspace.js +146 -0
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import { realpath } from "node:fs/promises";
|
|
3
|
+
import { redactExport } from "./privacy.js";
|
|
4
|
+
import { githubGraphql } from "./github.js";
|
|
5
|
+
class ReviewThreadError extends Error {
|
|
6
|
+
constructor(message, status = 400, ambiguous = false) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.status = status;
|
|
9
|
+
this.ambiguous = ambiguous;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
const ID = /^[A-Za-z0-9_=-]{1,200}$/;
|
|
13
|
+
const SHA = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/i;
|
|
14
|
+
const TTL = 20 * 6e4;
|
|
15
|
+
const THREAD_FIELDS = "id path line startLine originalLine originalStartLine diffSide startDiffSide isResolved isOutdated resolvedBy{login} viewerCanResolve viewerCanUnresolve";
|
|
16
|
+
const COMMENT_FIELDS = "id body createdAt updatedAt url author{login} replyTo{id}";
|
|
17
|
+
const PAGE_INFO = "pageInfo{hasNextPage endCursor} totalCount";
|
|
18
|
+
const LIST_QUERY = `query CodeflowReviewThreads($owner:String!,$name:String!,$number:Int!,$after:String){repository(owner:$owner,name:$name){pullRequest(number:$number){id number url state headRefOid reviewThreads(first:25,after:$after){nodes{${THREAD_FIELDS} comments(first:10){nodes{${COMMENT_FIELDS}} ${PAGE_INFO}}} ${PAGE_INFO}}}}}`;
|
|
19
|
+
const THREAD_QUERY = `query CodeflowReviewThread($id:ID!,$after:String){node(id:$id){... on PullRequestReviewThread{${THREAD_FIELDS} pullRequest{id number url state headRefOid} comments(first:25,after:$after){nodes{${COMMENT_FIELDS}} ${PAGE_INFO}}}}}`;
|
|
20
|
+
const mutation = (resolved) => `mutation CodeflowSetReviewThread($id:ID!,$clientMutationId:String!){${resolved ? "resolveReviewThread" : "unresolveReviewThread"}(input:{threadId:$id,clientMutationId:$clientMutationId}){thread{id isResolved}}}`;
|
|
21
|
+
const hash = (value) => createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
22
|
+
const text = (value, max) => typeof value === "string" ? value.slice(0, max) : "";
|
|
23
|
+
const line = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : void 0;
|
|
24
|
+
const object = (value) => !!value && typeof value === "object" && !Array.isArray(value);
|
|
25
|
+
function identity(value, field) {
|
|
26
|
+
if (typeof value !== "string" || !ID.test(value)) throw new ReviewThreadError(`GitHub returned an invalid ${field}`, 502);
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
function connection(value, cap) {
|
|
30
|
+
if (!object(value) || !Array.isArray(value.nodes) || value.nodes.length > cap || !object(value.pageInfo) || typeof value.pageInfo.hasNextPage !== "boolean" || !Number.isSafeInteger(value.totalCount) || value.totalCount < value.nodes.length || value.pageInfo.hasNextPage && (typeof value.pageInfo.endCursor !== "string" || !value.pageInfo.endCursor || value.pageInfo.endCursor.length > 2e3)) throw new ReviewThreadError("GitHub returned an invalid bounded connection", 502);
|
|
31
|
+
return value;
|
|
32
|
+
}
|
|
33
|
+
function pr(value, target) {
|
|
34
|
+
if (!object(value) || value.number !== target.number || typeof value.headRefOid !== "string" || !SHA.test(value.headRefOid) || !["OPEN", "CLOSED", "MERGED"].includes(value.state) || value.url !== target.url) throw new ReviewThreadError("GitHub pull request identity does not match this run", 409);
|
|
35
|
+
return { id: identity(value.id, "pull request ID"), head: value.headRefOid, state: value.state };
|
|
36
|
+
}
|
|
37
|
+
function threadIdentity(raw) {
|
|
38
|
+
if (!object(raw) || typeof raw.path !== "string" || raw.path.length > 2e3 || !["LEFT", "RIGHT"].includes(raw.diffSide) || typeof raw.isResolved !== "boolean" || typeof raw.isOutdated !== "boolean" || typeof raw.viewerCanResolve !== "boolean" || typeof raw.viewerCanUnresolve !== "boolean") throw new ReviewThreadError("GitHub returned an invalid review thread", 502);
|
|
39
|
+
return { id: identity(raw.id, "thread ID"), path: raw.path, line: line(raw.line), startLine: line(raw.startLine), originalLine: line(raw.originalLine), originalStartLine: line(raw.originalStartLine), side: raw.diffSide, startSide: ["LEFT", "RIGHT"].includes(raw.startDiffSide) ? raw.startDiffSide : void 0, resolved: raw.isResolved, outdated: raw.isOutdated, resolvedBy: text(raw.resolvedBy?.login, 100) || void 0, canResolve: raw.viewerCanResolve, canUnresolve: raw.viewerCanUnresolve };
|
|
40
|
+
}
|
|
41
|
+
function commentIdentity(raw) {
|
|
42
|
+
if (!object(raw) || typeof raw.body !== "string" || raw.body.length > 256 * 1024 || typeof raw.updatedAt !== "string" || typeof raw.createdAt !== "string") throw new ReviewThreadError("GitHub returned an invalid bounded review comment", 502);
|
|
43
|
+
let url;
|
|
44
|
+
try {
|
|
45
|
+
const parsed = new URL(text(raw.url, 2e3));
|
|
46
|
+
if (parsed.protocol === "https:" && !parsed.username && !parsed.password) url = parsed.href;
|
|
47
|
+
} catch {
|
|
48
|
+
}
|
|
49
|
+
return { id: identity(raw.id, "comment ID"), author: text(raw.author?.login, 100), body: raw.body, createdAt: raw.createdAt, updatedAt: raw.updatedAt, url, replyTo: raw.replyTo ? identity(raw.replyTo.id, "reply ID") : void 0 };
|
|
50
|
+
}
|
|
51
|
+
function present(raw, comments, totalComments, complete, budget) {
|
|
52
|
+
const info = threadIdentity(raw), originals = comments.map(commentIdentity);
|
|
53
|
+
if (new Set(originals.map((comment) => comment.id)).size !== originals.length) throw new ReviewThreadError("GitHub repeated a review comment while paginating; refresh the thread", 409);
|
|
54
|
+
const revision = hash({ ...info, totalComments, comments: originals });
|
|
55
|
+
return { ...info, revision, totalComments, commentsTruncated: !complete, comments: originals.map((comment) => {
|
|
56
|
+
const limit = Math.max(0, Math.min(8e3, budget.remaining)), body = redactExport(comment.body).slice(0, limit), bodyTruncated = body.length < comment.body.length;
|
|
57
|
+
budget.remaining -= body.length;
|
|
58
|
+
budget.truncated ||= bodyTruncated;
|
|
59
|
+
return { ...comment, body, bodyTruncated, trusted: false };
|
|
60
|
+
}) };
|
|
61
|
+
}
|
|
62
|
+
class GithubReviewThreads {
|
|
63
|
+
constructor(graphql = githubGraphql, now = Date.now) {
|
|
64
|
+
this.graphql = graphql;
|
|
65
|
+
this.now = now;
|
|
66
|
+
}
|
|
67
|
+
snapshots = /* @__PURE__ */ new Map();
|
|
68
|
+
busy = /* @__PURE__ */ new Set();
|
|
69
|
+
async target(repo, url) {
|
|
70
|
+
let parsed;
|
|
71
|
+
try {
|
|
72
|
+
parsed = new URL(url);
|
|
73
|
+
} catch {
|
|
74
|
+
throw new ReviewThreadError("This run has no valid GitHub pull request URL");
|
|
75
|
+
}
|
|
76
|
+
const match = parsed.pathname.match(/^\/([A-Za-z0-9_.-]+)\/([A-Za-z0-9_.-]+)\/pull\/([1-9]\d{0,9})\/?$/);
|
|
77
|
+
if (parsed.protocol !== "https:" || parsed.username || parsed.password || parsed.search || parsed.hash || !match || !/^[a-z0-9.-]+$/i.test(parsed.hostname)) throw new ReviewThreadError("Use this run's stored HTTPS GitHub pull request URL");
|
|
78
|
+
return { repo: await realpath(repo), host: parsed.host, owner: match[1], name: match[2], number: Number(match[3]), url: parsed.href.replace(/\/$/, "") };
|
|
79
|
+
}
|
|
80
|
+
prune(reserve = false) {
|
|
81
|
+
for (const [id, item] of this.snapshots) if (item.expiresAt <= this.now()) this.snapshots.delete(id);
|
|
82
|
+
while (this.snapshots.size > (reserve ? 199 : 200)) this.snapshots.delete(this.snapshots.keys().next().value);
|
|
83
|
+
}
|
|
84
|
+
async query(target, query, variables, signal) {
|
|
85
|
+
signal?.throwIfAborted();
|
|
86
|
+
return this.graphql(target.repo, target.host, query, variables, { signal, timeoutMs: 1e4, maxOutputBytes: 8 * 1024 * 1024 });
|
|
87
|
+
}
|
|
88
|
+
async snapshot(repo, url, id) {
|
|
89
|
+
this.prune();
|
|
90
|
+
const item = this.snapshots.get(id), target = await this.target(repo, url);
|
|
91
|
+
if (!item || item.target.repo !== target.repo || item.target.url !== target.url) throw new ReviewThreadError("Review thread snapshot expired or belongs to another run repository. Refresh the threads.", 409);
|
|
92
|
+
return item;
|
|
93
|
+
}
|
|
94
|
+
async list(repo, url, signal) {
|
|
95
|
+
const target = await this.target(repo, url), threads = [], budget = { remaining: 2 * 1024 * 1024, truncated: false }, seen = /* @__PURE__ */ new Set();
|
|
96
|
+
let initial, after, totalThreads = 0, truncated = false;
|
|
97
|
+
for (let page = 0; page < 4; page++) {
|
|
98
|
+
const data = await this.query(target, LIST_QUERY, { owner: target.owner, name: target.name, number: target.number, after: after ?? null }, signal), raw = data.repository?.pullRequest, info = pr(raw, target);
|
|
99
|
+
if (initial && (initial.head !== info.head || initial.id !== info.id || initial.state !== info.state)) throw new ReviewThreadError("The pull request changed while threads were loading. Refresh before reviewing.", 409);
|
|
100
|
+
initial ??= info;
|
|
101
|
+
const list = connection(raw.reviewThreads, 25);
|
|
102
|
+
totalThreads = list.totalCount;
|
|
103
|
+
for (const rawThread of list.nodes) {
|
|
104
|
+
const comments = connection(rawThread.comments, 10), thread = present(rawThread, comments.nodes, comments.totalCount, !comments.pageInfo.hasNextPage && comments.nodes.length === comments.totalCount, budget);
|
|
105
|
+
if (seen.has(thread.id)) throw new ReviewThreadError("GitHub repeated a thread while paginating; refresh the list", 409);
|
|
106
|
+
seen.add(thread.id);
|
|
107
|
+
threads.push(thread);
|
|
108
|
+
}
|
|
109
|
+
if (!list.pageInfo.hasNextPage) break;
|
|
110
|
+
if (after === list.pageInfo.endCursor) throw new ReviewThreadError("GitHub repeated the review thread cursor", 502);
|
|
111
|
+
after = list.pageInfo.endCursor;
|
|
112
|
+
truncated = page === 3;
|
|
113
|
+
}
|
|
114
|
+
this.prune(true);
|
|
115
|
+
const snapshot = { id: randomBytes(20).toString("hex"), target, prId: initial.id, head: initial.head, expiresAt: this.now() + TTL, threads: new Map(threads.map((thread) => [thread.id, { revision: thread.revision, complete: !thread.commentsTruncated }])) };
|
|
116
|
+
this.snapshots.set(snapshot.id, snapshot);
|
|
117
|
+
return { snapshotId: snapshot.id, prId: snapshot.prId, url: target.url, number: target.number, head: snapshot.head, state: initial.state, threads, totalThreads, truncated, textTruncated: budget.truncated, expiresAt: snapshot.expiresAt, trusted: false };
|
|
118
|
+
}
|
|
119
|
+
async current(snapshot, id, signal, requireOpen = false) {
|
|
120
|
+
let raw, comments = [], after, total = 0, meta;
|
|
121
|
+
for (let page = 0; page < 4; page++) {
|
|
122
|
+
const data = await this.query(snapshot.target, THREAD_QUERY, { id, after: after ?? null }, signal), item = data.node;
|
|
123
|
+
const info = pr(item?.pullRequest, snapshot.target);
|
|
124
|
+
if (info.id !== snapshot.prId || info.head !== snapshot.head || requireOpen && info.state !== "OPEN") throw new ReviewThreadError("The pull request head or state changed. Refresh before changing thread resolution.", 409);
|
|
125
|
+
const identity2 = threadIdentity(item);
|
|
126
|
+
if (identity2.id !== id) throw new ReviewThreadError("GitHub returned a different review thread", 409);
|
|
127
|
+
const serialized = JSON.stringify(identity2), list = connection(item.comments, 25);
|
|
128
|
+
if (meta !== void 0 && (meta !== serialized || total !== list.totalCount)) throw new ReviewThreadError("The review thread changed while its replies were loading. Refresh it.", 409);
|
|
129
|
+
meta = serialized;
|
|
130
|
+
total = list.totalCount;
|
|
131
|
+
raw = item;
|
|
132
|
+
comments.push(...list.nodes);
|
|
133
|
+
if (!list.pageInfo.hasNextPage) return present(raw, comments, total, comments.length === total, { remaining: 8e5, truncated: false });
|
|
134
|
+
if (after === list.pageInfo.endCursor) throw new ReviewThreadError("GitHub repeated the review comment cursor", 502);
|
|
135
|
+
after = list.pageInfo.endCursor;
|
|
136
|
+
}
|
|
137
|
+
return present(raw, comments, total, false, { remaining: 8e5, truncated: false });
|
|
138
|
+
}
|
|
139
|
+
async detail(repo, url, input, signal) {
|
|
140
|
+
const snapshot = await this.snapshot(repo, url, input.snapshotId);
|
|
141
|
+
if (!snapshot.threads.has(input.threadId)) throw new ReviewThreadError("This thread was not in the server-issued snapshot", 409);
|
|
142
|
+
const thread = await this.current(snapshot, input.threadId, signal);
|
|
143
|
+
snapshot.threads.set(thread.id, { revision: thread.revision, complete: !thread.commentsTruncated });
|
|
144
|
+
return thread;
|
|
145
|
+
}
|
|
146
|
+
async setResolved(repo, url, input, signal) {
|
|
147
|
+
if (!input || typeof input.snapshotId !== "string" || typeof input.threadId !== "string" || !ID.test(input.threadId) || !SHA.test(input.expectedHead) || !/^[a-f0-9]{64}$/.test(input.expectedRevision) || typeof input.resolved !== "boolean") throw new ReviewThreadError("Provide a current server snapshot, exact PR head, thread revision and explicit resolution state");
|
|
148
|
+
const snapshot = await this.snapshot(repo, url, input.snapshotId), key = `${snapshot.target.repo}:${snapshot.prId}:${input.threadId}`;
|
|
149
|
+
if (this.busy.has(key)) throw new ReviewThreadError("This thread already has a resolution action in progress", 409);
|
|
150
|
+
this.busy.add(key);
|
|
151
|
+
try {
|
|
152
|
+
const selected = snapshot.threads.get(input.threadId);
|
|
153
|
+
if (!selected || snapshot.head !== input.expectedHead || selected.revision !== input.expectedRevision || !selected.complete) throw new ReviewThreadError("Refresh the complete current thread before changing its resolution", 409);
|
|
154
|
+
const current = await this.current(snapshot, input.threadId, signal, true);
|
|
155
|
+
if (current.commentsTruncated || current.revision !== selected.revision) throw new ReviewThreadError("The thread or its replies changed. Review the fresh discussion before changing resolution.", 409);
|
|
156
|
+
if (current.resolved === input.resolved) throw new ReviewThreadError("The thread already has that resolution state. Refresh it.", 409);
|
|
157
|
+
if (input.resolved ? !current.canResolve : !current.canUnresolve) throw new ReviewThreadError("Your current GitHub login cannot change this thread's resolution", 403);
|
|
158
|
+
for (const [id, saved] of this.snapshots) if (saved.target.repo === snapshot.target.repo && saved.prId === snapshot.prId) this.snapshots.delete(id);
|
|
159
|
+
let response;
|
|
160
|
+
try {
|
|
161
|
+
response = await this.query(snapshot.target, mutation(input.resolved), { id: input.threadId, clientMutationId: randomBytes(16).toString("hex") }, signal);
|
|
162
|
+
} catch (error) {
|
|
163
|
+
throw new ReviewThreadError(`GitHub resolution outcome may be ambiguous. Refresh the thread before another action. ${error.message}`, 502, true);
|
|
164
|
+
}
|
|
165
|
+
const changed = response?.[input.resolved ? "resolveReviewThread" : "unresolveReviewThread"]?.thread;
|
|
166
|
+
if (changed?.id !== input.threadId || changed?.isResolved !== input.resolved) throw new ReviewThreadError("GitHub did not confirm the requested resolution; refresh before another action", 502, true);
|
|
167
|
+
try {
|
|
168
|
+
const thread = await this.current(snapshot, input.threadId, signal, true);
|
|
169
|
+
return { applied: true, thread, refreshRequired: true, ...thread.resolved !== input.resolved ? { warning: "GitHub changed this thread again after the action. Refresh its current state." } : {} };
|
|
170
|
+
} catch {
|
|
171
|
+
return { applied: true, refreshRequired: true, warning: "GitHub confirmed the action, but its current head or thread state could not be rechecked. Refresh before further actions." };
|
|
172
|
+
}
|
|
173
|
+
} finally {
|
|
174
|
+
this.busy.delete(key);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
export {
|
|
179
|
+
GithubReviewThreads,
|
|
180
|
+
ReviewThreadError
|
|
181
|
+
};
|