carbonless-review 0.2.0 → 0.3.0-rc.10
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/build-info.json +2 -2
- package/dist/client.js +5 -0
- package/dist/server/agent/checkout.js +18 -3
- package/dist/server/agent/claim.js +55 -0
- package/dist/server/agent/githubJoin.js +92 -0
- package/dist/server/agent/host.js +732 -42
- package/dist/server/agent/join.js +552 -0
- package/dist/server/agent/prompt.js +154 -15
- package/dist/server/agent/runner.js +416 -50
- package/dist/server/agent/store.js +114 -10
- package/dist/server/app.js +211 -13
- package/dist/server/diff/snapshot.js +9 -0
- package/dist/server/gh.js +19 -2
- package/dist/server/git.js +9 -0
- package/dist/server/home/homeService.js +77 -17
- package/dist/server/home/inbox.js +4 -0
- package/dist/server/home/people.js +15 -0
- package/dist/server/home/search.js +13 -0
- package/dist/server/home/started.js +5 -0
- package/dist/server/mcp/reviews.js +108 -0
- package/dist/server/mcp/routes.js +206 -0
- package/dist/server/mcp/server.js +272 -0
- package/dist/server/mcp/watch.js +69 -0
- package/dist/server/overview.js +1 -0
- package/dist/server/registry.js +153 -8
- package/dist/server/rereview/engine.js +8 -1
- package/dist/server/rereview/threads.js +71 -13
- package/dist/server/review/events.js +128 -3
- package/dist/server/serve.js +15 -2
- package/dist/server/session.js +76 -4
- package/dist/server/testHooks.js +112 -0
- package/dist/server/threadWrites.js +1 -1
- package/dist/server/update/registry.js +48 -32
- package/dist/server/update/updater.js +71 -15
- package/dist/shared/agent.js +35 -0
- package/dist/shared/agentHarnesses.js +30 -0
- package/dist/shared/rereview/carry.js +10 -1
- package/dist/shared/rereview/placement.js +40 -2
- package/dist/shared/update.js +7 -0
- package/dist/web/assets/FilesPage-Bvt2iKhB.js +5 -0
- package/dist/web/assets/{GitBranchIcon-Fj_6Eiei.js → GitBranchIcon-Du_clZHi.js} +1 -1
- package/dist/web/assets/OverviewPage-DsbgPbqR.js +3 -0
- package/dist/web/assets/VerdictCard-B5-exMig.js +1 -0
- package/dist/web/assets/{VerdictPage-BTtH4a0O.js → VerdictPage-C2IqzLxs.js} +1 -1
- package/dist/web/assets/{WriteWarnings-DkEuoIiZ.js → WriteWarnings-i9ipioMM.js} +1 -1
- package/dist/web/assets/index-AwSnShNc.js +12 -0
- package/dist/web/assets/index-CCIeSgFG.css +2 -0
- package/dist/web/assets/openedOn-pLvJbixD.js +1 -0
- package/dist/web/assets/spanMarks-CRy1hSYo.js +1 -0
- package/dist/web/assets/useSetStates-CoC8bUvu.js +81 -0
- package/dist/web/index.html +4 -4
- package/package.json +8 -6
- package/dist/web/assets/FilesPage-DJBYm7xE.js +0 -5
- package/dist/web/assets/OverviewPage-CM2wVf3D.js +0 -3
- package/dist/web/assets/VerdictCard-CzCrGQFB.js +0 -1
- package/dist/web/assets/index-BPxKN2ws.css +0 -2
- package/dist/web/assets/index-Cv0_7JTL.js +0 -11
- package/dist/web/assets/landing-BSkT9Wga.js +0 -1
- package/dist/web/assets/spanMarks-CB_mfKde.js +0 -72
- package/dist/web/assets/useSetStates-BHCUczWh.js +0 -10
package/dist/build-info.json
CHANGED
package/dist/client.js
CHANGED
|
@@ -7,6 +7,7 @@ import { parsePrArg, resolvePrRef } from "./server/gh.js";
|
|
|
7
7
|
import { stateDir } from "./server/paths.js";
|
|
8
8
|
import { parseHead } from "./server/pinnedHead.js";
|
|
9
9
|
import { accessKey, readLiveServerState, } from "./server/serverState.js";
|
|
10
|
+
import { parseTraceparent } from "./server/trace.js";
|
|
10
11
|
import { BUILD, mismatchMessage, sameBuild, VERSION_HEADER, } from "./server/version.js";
|
|
11
12
|
/**
|
|
12
13
|
* `crb <pr>`, the thin client: finds this machine's `crb serve` through
|
|
@@ -148,6 +149,10 @@ export async function runClient(req, deps = {}) {
|
|
|
148
149
|
headers: {
|
|
149
150
|
"content-type": "application/json",
|
|
150
151
|
[VERSION_HEADER]: JSON.stringify(build),
|
|
152
|
+
// The server's span of this open goes under the caller's (a test's).
|
|
153
|
+
...(parseTraceparent(process.env.TRACEPARENT)
|
|
154
|
+
? { traceparent: process.env.TRACEPARENT }
|
|
155
|
+
: {}),
|
|
151
156
|
...(await authHeaders(server)),
|
|
152
157
|
},
|
|
153
158
|
body: JSON.stringify({
|
|
@@ -69,10 +69,15 @@ export async function createCheckout(cacheClone, reviewId, head) {
|
|
|
69
69
|
},
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* What a review's agent dir keeps across servers (see join.ts): the spawn
|
|
74
|
+
* flag, a joined session, and a leftover `acp-off` (ignored, never deleted).
|
|
75
|
+
*/
|
|
76
|
+
const KEEP = new Set(["acp-on", "acp-off", "joined.json"]);
|
|
72
77
|
/**
|
|
73
78
|
* Removes `<agentsDir>/<review id>` dirs not in `live` whose claim is free
|
|
74
|
-
* or names a dead pid (left by a server that stopped)
|
|
75
|
-
* left alone.
|
|
79
|
+
* or names a dead pid (left by a server that stopped), all but their
|
|
80
|
+
* `acp-on`, `acp-off` and `joined.json`. Other names are left alone.
|
|
76
81
|
*/
|
|
77
82
|
export async function sweepStaleCheckouts(live) {
|
|
78
83
|
let names;
|
|
@@ -89,7 +94,17 @@ export async function sweepStaleCheckouts(live) {
|
|
|
89
94
|
const claim = await readClaim(dir);
|
|
90
95
|
if (claim && pidAlive(claim.pid))
|
|
91
96
|
return;
|
|
92
|
-
|
|
97
|
+
// A joined session and the spawn flag outlive a server
|
|
98
|
+
// (join.ts): only they stay.
|
|
99
|
+
const entries = await readdir(dir).catch(() => []);
|
|
100
|
+
const kept = entries.filter((e) => KEEP.has(e));
|
|
101
|
+
if (kept.length === 0) {
|
|
102
|
+
await rm(dir, { recursive: true, force: true });
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
await Promise.all(entries
|
|
106
|
+
.filter((e) => !KEEP.has(e))
|
|
107
|
+
.map((e) => rm(join(dir, e), { recursive: true, force: true })));
|
|
93
108
|
}));
|
|
94
109
|
}
|
|
95
110
|
//# sourceMappingURL=checkout.js.map
|
|
@@ -126,4 +126,59 @@ async function readText(path) {
|
|
|
126
126
|
return null;
|
|
127
127
|
}
|
|
128
128
|
}
|
|
129
|
+
/**
|
|
130
|
+
* A review's claim as one server shares it between its ACP runner and its
|
|
131
|
+
* joined session (the spec: "the existing claim also covers a joined
|
|
132
|
+
* session"): taken on the first hold, released when the last user drops.
|
|
133
|
+
*/
|
|
134
|
+
export class ReviewClaim {
|
|
135
|
+
dir;
|
|
136
|
+
self;
|
|
137
|
+
#users = new Set();
|
|
138
|
+
#release = null;
|
|
139
|
+
/** One take at a time (two users holding at once take once). */
|
|
140
|
+
#taking = null;
|
|
141
|
+
/** A release under way: a hold waits for it (else it sees its own claim as another's). */
|
|
142
|
+
#releasing = null;
|
|
143
|
+
constructor(dir, self) {
|
|
144
|
+
this.dir = dir;
|
|
145
|
+
this.self = self;
|
|
146
|
+
}
|
|
147
|
+
/** Whether this server holds the claim (for any user). */
|
|
148
|
+
held() {
|
|
149
|
+
return this.#release !== null;
|
|
150
|
+
}
|
|
151
|
+
/** Holds the claim for `user`; `{ ok: false, holder }` when another server has it. */
|
|
152
|
+
async hold(user) {
|
|
153
|
+
while (this.#releasing && !this.#release)
|
|
154
|
+
await this.#releasing;
|
|
155
|
+
if (!this.#release) {
|
|
156
|
+
this.#taking ??= takeClaim(this.dir, this.self()).finally(() => {
|
|
157
|
+
this.#taking = null;
|
|
158
|
+
});
|
|
159
|
+
const taken = await this.#taking;
|
|
160
|
+
if (!taken.ok)
|
|
161
|
+
return taken;
|
|
162
|
+
this.#release ??= taken.release;
|
|
163
|
+
}
|
|
164
|
+
this.#users.add(user);
|
|
165
|
+
return { ok: true, release: () => this.drop(user) };
|
|
166
|
+
}
|
|
167
|
+
/** `user` lets go; the claim is released once nobody holds it. */
|
|
168
|
+
async drop(user) {
|
|
169
|
+
this.#users.delete(user);
|
|
170
|
+
if (this.#users.size > 0 || !this.#release)
|
|
171
|
+
return;
|
|
172
|
+
const release = this.#release;
|
|
173
|
+
this.#release = null;
|
|
174
|
+
const releasing = release()
|
|
175
|
+
.catch(() => { })
|
|
176
|
+
.finally(() => {
|
|
177
|
+
if (this.#releasing === releasing)
|
|
178
|
+
this.#releasing = null;
|
|
179
|
+
});
|
|
180
|
+
this.#releasing = releasing;
|
|
181
|
+
await releasing;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
129
184
|
//# sourceMappingURL=claim.js.map
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
const OID = /^[0-9a-f]{40}([0-9a-f]{24})?$/;
|
|
2
|
+
/** The GitHub threads a conversation can still belong to: listed, with an id, not deleted. */
|
|
3
|
+
export function liveGithubThreads(threads) {
|
|
4
|
+
const live = new Map();
|
|
5
|
+
for (const t of threads) {
|
|
6
|
+
if (t.id !== "" && t.unavailable !== "deleted")
|
|
7
|
+
live.set(t.id, t);
|
|
8
|
+
}
|
|
9
|
+
return live;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* The folds as the page and the runner see them: a live thread
|
|
13
|
+
* conversation is resolved exactly when its GitHub thread is (its own
|
|
14
|
+
* agent-thread-resolved events count for nothing); one whose GitHub
|
|
15
|
+
* thread is gone (not listed, or deleted) is an orphan: a span thread on
|
|
16
|
+
* its recorded lines, resolved by its own events. Span threads pass
|
|
17
|
+
* through unchanged.
|
|
18
|
+
*/
|
|
19
|
+
export function joinGithub(folds, threads) {
|
|
20
|
+
const live = liveGithubThreads(threads);
|
|
21
|
+
return folds.map((f) => {
|
|
22
|
+
if (f.anchor.subject !== "thread")
|
|
23
|
+
return f;
|
|
24
|
+
const rt = live.get(f.anchor.thread);
|
|
25
|
+
if (rt)
|
|
26
|
+
return { ...f, resolved: rt.resolved };
|
|
27
|
+
return { ...f, anchor: orphanAnchor(f.anchor), orphaned: true };
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The folds joined with the threads `read` gives, read only when a thread
|
|
32
|
+
* conversation is among them. When GitHub can't be read, thread
|
|
33
|
+
* conversations stay live (where their thread is), flagged
|
|
34
|
+
* `githubUnknown`, with their own stored `resolved`: never a made-up one.
|
|
35
|
+
*/
|
|
36
|
+
export async function joinRead(folds, read) {
|
|
37
|
+
if (!folds.some((f) => f.anchor.subject === "thread"))
|
|
38
|
+
return folds;
|
|
39
|
+
const threads = await read().catch(() => null);
|
|
40
|
+
if (threads)
|
|
41
|
+
return joinGithub(folds, threads);
|
|
42
|
+
return folds.map((f) => f.anchor.subject === "thread" ? { ...f, githubUnknown: true } : f);
|
|
43
|
+
}
|
|
44
|
+
/** The span anchor an orphan falls back to: line anchor on the recorded lines at `commit`, else a file anchor. */
|
|
45
|
+
export function orphanAnchor(a) {
|
|
46
|
+
const { path, commit, side, line } = a;
|
|
47
|
+
if (commit === "" || side === undefined || line === undefined) {
|
|
48
|
+
return { subject: "file", path, commit };
|
|
49
|
+
}
|
|
50
|
+
// A start on another side, or none: as recorded, else the one line.
|
|
51
|
+
const start = a.startSide !== undefined && a.startLine !== undefined
|
|
52
|
+
? { startSide: a.startSide, startLine: a.startLine }
|
|
53
|
+
: { startSide: side, startLine: line };
|
|
54
|
+
return { subject: "line", path, commit, side, line, ...start };
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* left/right of a view, from the session's patch sets and `base` (the
|
|
58
|
+
* snapshot's merge base): the right is the viewed patch set's head or the
|
|
59
|
+
* viewed head; the left is the compared-from patch set's head, or for the
|
|
60
|
+
* base branch the viewed patch set's merge base (a head's patch set's, else
|
|
61
|
+
* `base`). Null for a patch set that isn't one, or a head that isn't an OID.
|
|
62
|
+
*/
|
|
63
|
+
export function resolveView(view, patchsets, base) {
|
|
64
|
+
const ps = (n) => patchsets.find((p) => p.number === n);
|
|
65
|
+
let right;
|
|
66
|
+
let mergeBase;
|
|
67
|
+
if ("patchset" in view.target) {
|
|
68
|
+
const target = ps(view.target.patchset);
|
|
69
|
+
if (!target)
|
|
70
|
+
return null;
|
|
71
|
+
right = target.head;
|
|
72
|
+
mergeBase = target.mergeBase;
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
const head = view.target.head;
|
|
76
|
+
if (!OID.test(head))
|
|
77
|
+
return null;
|
|
78
|
+
right = head;
|
|
79
|
+
mergeBase = patchsets.find((p) => p.head === head)?.mergeBase ?? base;
|
|
80
|
+
}
|
|
81
|
+
let left;
|
|
82
|
+
if ("branch" in view.from)
|
|
83
|
+
left = mergeBase;
|
|
84
|
+
else {
|
|
85
|
+
const from = ps(view.from.patchset);
|
|
86
|
+
if (!from)
|
|
87
|
+
return null;
|
|
88
|
+
left = from.head;
|
|
89
|
+
}
|
|
90
|
+
return { target: view.target, from: view.from, left, right };
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=githubJoin.js.map
|