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.
Files changed (60) hide show
  1. package/dist/build-info.json +2 -2
  2. package/dist/client.js +5 -0
  3. package/dist/server/agent/checkout.js +18 -3
  4. package/dist/server/agent/claim.js +55 -0
  5. package/dist/server/agent/githubJoin.js +92 -0
  6. package/dist/server/agent/host.js +732 -42
  7. package/dist/server/agent/join.js +552 -0
  8. package/dist/server/agent/prompt.js +154 -15
  9. package/dist/server/agent/runner.js +416 -50
  10. package/dist/server/agent/store.js +114 -10
  11. package/dist/server/app.js +211 -13
  12. package/dist/server/diff/snapshot.js +9 -0
  13. package/dist/server/gh.js +19 -2
  14. package/dist/server/git.js +9 -0
  15. package/dist/server/home/homeService.js +77 -17
  16. package/dist/server/home/inbox.js +4 -0
  17. package/dist/server/home/people.js +15 -0
  18. package/dist/server/home/search.js +13 -0
  19. package/dist/server/home/started.js +5 -0
  20. package/dist/server/mcp/reviews.js +108 -0
  21. package/dist/server/mcp/routes.js +206 -0
  22. package/dist/server/mcp/server.js +272 -0
  23. package/dist/server/mcp/watch.js +69 -0
  24. package/dist/server/overview.js +1 -0
  25. package/dist/server/registry.js +153 -8
  26. package/dist/server/rereview/engine.js +8 -1
  27. package/dist/server/rereview/threads.js +71 -13
  28. package/dist/server/review/events.js +128 -3
  29. package/dist/server/serve.js +15 -2
  30. package/dist/server/session.js +76 -4
  31. package/dist/server/testHooks.js +112 -0
  32. package/dist/server/threadWrites.js +1 -1
  33. package/dist/server/update/registry.js +48 -32
  34. package/dist/server/update/updater.js +71 -15
  35. package/dist/shared/agent.js +35 -0
  36. package/dist/shared/agentHarnesses.js +30 -0
  37. package/dist/shared/rereview/carry.js +10 -1
  38. package/dist/shared/rereview/placement.js +40 -2
  39. package/dist/shared/update.js +7 -0
  40. package/dist/web/assets/FilesPage-Bvt2iKhB.js +5 -0
  41. package/dist/web/assets/{GitBranchIcon-Fj_6Eiei.js → GitBranchIcon-Du_clZHi.js} +1 -1
  42. package/dist/web/assets/OverviewPage-DsbgPbqR.js +3 -0
  43. package/dist/web/assets/VerdictCard-B5-exMig.js +1 -0
  44. package/dist/web/assets/{VerdictPage-BTtH4a0O.js → VerdictPage-C2IqzLxs.js} +1 -1
  45. package/dist/web/assets/{WriteWarnings-DkEuoIiZ.js → WriteWarnings-i9ipioMM.js} +1 -1
  46. package/dist/web/assets/index-AwSnShNc.js +12 -0
  47. package/dist/web/assets/index-CCIeSgFG.css +2 -0
  48. package/dist/web/assets/openedOn-pLvJbixD.js +1 -0
  49. package/dist/web/assets/spanMarks-CRy1hSYo.js +1 -0
  50. package/dist/web/assets/useSetStates-CoC8bUvu.js +81 -0
  51. package/dist/web/index.html +4 -4
  52. package/package.json +8 -6
  53. package/dist/web/assets/FilesPage-DJBYm7xE.js +0 -5
  54. package/dist/web/assets/OverviewPage-CM2wVf3D.js +0 -3
  55. package/dist/web/assets/VerdictCard-CzCrGQFB.js +0 -1
  56. package/dist/web/assets/index-BPxKN2ws.css +0 -2
  57. package/dist/web/assets/index-Cv0_7JTL.js +0 -11
  58. package/dist/web/assets/landing-BSkT9Wga.js +0 -1
  59. package/dist/web/assets/spanMarks-CB_mfKde.js +0 -72
  60. package/dist/web/assets/useSetStates-BHCUczWh.js +0 -10
@@ -1,4 +1,4 @@
1
1
  {
2
- "version": "0.2.0",
3
- "commit": "f159e7d32a535e53cb2dd9d801429b580852bce4"
2
+ "version": "0.3.0-rc.10",
3
+ "commit": "4010c8c698544e8e1e9d385657fe1180dbfd8410"
4
4
  }
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). Other names are
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
- await rm(dir, { recursive: true, force: true });
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