agents-relay 1.0.138 → 1.0.139
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/pool.js +11 -2
- package/dist/relayd.js +11 -8
- package/dist/store-github.js +7 -1
- package/dist/store.js +28 -11
- package/package.json +1 -1
package/dist/pool.js
CHANGED
|
@@ -28,6 +28,7 @@ export class RepositoryWorkerPool {
|
|
|
28
28
|
reconcileLocks = new Map();
|
|
29
29
|
targetedReconcileFlights = new Map();
|
|
30
30
|
jobTargets = new Map();
|
|
31
|
+
targetSeeds = new Map();
|
|
31
32
|
constructor(client, repository, trustedAuthors, concurrency, makeReconciler, isRateLimited = () => false) {
|
|
32
33
|
this.client = client;
|
|
33
34
|
this.trustedAuthors = trustedAuthors;
|
|
@@ -99,6 +100,11 @@ export class RepositoryWorkerPool {
|
|
|
99
100
|
this.running = false;
|
|
100
101
|
}
|
|
101
102
|
}
|
|
103
|
+
seedTargetSnapshot(repository, prNumber, jobId, seed) {
|
|
104
|
+
if (!this.repositories.includes(repository))
|
|
105
|
+
return;
|
|
106
|
+
this.targetSeeds.set(`${repository}#${prNumber}:${jobId}`, seed);
|
|
107
|
+
}
|
|
102
108
|
async reconcileTarget(repository, prNumber, jobId) {
|
|
103
109
|
if (this.isRateLimited() || !this.repositories.includes(repository))
|
|
104
110
|
return;
|
|
@@ -108,8 +114,11 @@ export class RepositoryWorkerPool {
|
|
|
108
114
|
if (existing)
|
|
109
115
|
return existing;
|
|
110
116
|
const flight = this.reconcileExclusive(jobId, async () => {
|
|
111
|
-
const
|
|
112
|
-
|
|
117
|
+
const seedKey = `${repository}#${prNumber}:${jobId}`;
|
|
118
|
+
const seed = this.targetSeeds.get(seedKey) ?? null;
|
|
119
|
+
this.targetSeeds.delete(seedKey);
|
|
120
|
+
const store = new GitHubStore(this.client, repository, prNumber, this.trustedAuthors, seed);
|
|
121
|
+
await withGitHubRequestContext(this.client, seed ? 'pool.targeted-reconcile.seeded' : 'pool.targeted-reconcile', () => this.makeReconciler(store, Math.max(1, this.concurrency)).reconcile(jobId));
|
|
113
122
|
});
|
|
114
123
|
this.targetedReconcileFlights.set(key, flight);
|
|
115
124
|
try {
|
package/dist/relayd.js
CHANGED
|
@@ -9,7 +9,7 @@ import { serveDashboard } from './dashboard.js';
|
|
|
9
9
|
import { NatsEventBus } from './events.js';
|
|
10
10
|
import { RepositoryWorkerPool, aggregateManagedGitHubJobs } from './pool.js';
|
|
11
11
|
import { Reconciler } from './reconciler.js';
|
|
12
|
-
import { dashboardManagedJobs, GitHubStore, InMemoryStore,
|
|
12
|
+
import { dashboardManagedJobs, GitHubStore, InMemoryStore, loadManagedGitHubPullRequestSnapshotGraphql, withGitHubRequestContext } from './store.js';
|
|
13
13
|
import { githubAuthContext } from './github-auth.js';
|
|
14
14
|
import { dashboardJobAction, isEntrypoint, runtimePlanner, runtimeReviewer, startService, SERVICE_DEFAULTS } from './cli.js';
|
|
15
15
|
import { dashboardTaskAction, dashboardTaskSetState } from './dashboard-actions.js';
|
|
@@ -143,21 +143,21 @@ export async function runDaemon(argv) {
|
|
|
143
143
|
catch { /* cache is best effort */ }
|
|
144
144
|
};
|
|
145
145
|
const pullRequestRefreshFlights = new Map();
|
|
146
|
-
const
|
|
146
|
+
const refreshPullRequestSnapshot = async (repository, prNumber) => {
|
|
147
147
|
if (client.rateLimitStatus().limited)
|
|
148
|
-
return cachedOverviews.filter(item => item.job.repository === repository && item.job.prNumber === prNumber);
|
|
148
|
+
return { overviews: cachedOverviews.filter(item => item.job.repository === repository && item.job.prNumber === prNumber), seed: { prNode: {}, comments: null } };
|
|
149
149
|
const key = `${repository}#${prNumber}`;
|
|
150
150
|
const existing = pullRequestRefreshFlights.get(key);
|
|
151
151
|
if (existing)
|
|
152
152
|
return existing;
|
|
153
153
|
const flight = (async () => {
|
|
154
154
|
githubReadPaths.prRefreshes += 1;
|
|
155
|
-
const
|
|
155
|
+
const snapshot = await withGitHubRequestContext(client, 'relayd.pr-refresh', () => loadManagedGitHubPullRequestSnapshotGraphql(client, repository, prNumber, trusted));
|
|
156
156
|
cachedOverviews = cachedOverviews.filter(item => item.job.repository !== repository || item.job.prNumber !== prNumber);
|
|
157
|
-
cachedOverviews.push(...
|
|
157
|
+
cachedOverviews.push(...snapshot.overviews);
|
|
158
158
|
cachedOverviews = dashboardManagedJobs(cachedOverviews);
|
|
159
159
|
await persistCache();
|
|
160
|
-
return
|
|
160
|
+
return snapshot;
|
|
161
161
|
})();
|
|
162
162
|
pullRequestRefreshFlights.set(key, flight);
|
|
163
163
|
try {
|
|
@@ -168,6 +168,7 @@ export async function runDaemon(argv) {
|
|
|
168
168
|
pullRequestRefreshFlights.delete(key);
|
|
169
169
|
}
|
|
170
170
|
};
|
|
171
|
+
const refreshPullRequest = async (repository, prNumber) => (await refreshPullRequestSnapshot(repository, prNumber)).overviews;
|
|
171
172
|
const initial = cachedOverviews;
|
|
172
173
|
const fallback = initial[0]?.job;
|
|
173
174
|
const dashboardStore = fallback ? new GitHubStore(client, fallback.repository, fallback.prNumber, trusted) : new InMemoryStore();
|
|
@@ -206,8 +207,10 @@ export async function runDaemon(argv) {
|
|
|
206
207
|
refresh: async (event) => {
|
|
207
208
|
if (!event.repository || !event.pullRequest)
|
|
208
209
|
return [];
|
|
209
|
-
const
|
|
210
|
-
|
|
210
|
+
const snapshot = await refreshPullRequestSnapshot(event.repository, event.pullRequest);
|
|
211
|
+
for (const item of snapshot.overviews)
|
|
212
|
+
pool.seedTargetSnapshot(event.repository, event.pullRequest, item.job.id, snapshot.seed);
|
|
213
|
+
return snapshot.overviews.map(item => item.job.id);
|
|
211
214
|
},
|
|
212
215
|
wake: async (event) => {
|
|
213
216
|
if (!event.repository || !event.pullRequest)
|
package/dist/store-github.js
CHANGED
|
@@ -11,11 +11,17 @@ export class GitHubStore {
|
|
|
11
11
|
commentsCache = null;
|
|
12
12
|
commentsFlight = null;
|
|
13
13
|
clearReadSnapshot() { this.prNodeCache = null; this.commentsCache = null; this.commentsFlight = null; }
|
|
14
|
-
constructor(client, repository, prNumber, trustedAuthors = new Set()) {
|
|
14
|
+
constructor(client, repository, prNumber, trustedAuthors = new Set(), seed = null) {
|
|
15
15
|
this.client = client;
|
|
16
16
|
this.repository = repository;
|
|
17
17
|
this.prNumber = prNumber;
|
|
18
18
|
this.trustedAuthors = trustedAuthors;
|
|
19
|
+
if (seed) {
|
|
20
|
+
if (Object.keys(seed.prNode).length > 0)
|
|
21
|
+
this.prNodeCache = Promise.resolve(seed.prNode);
|
|
22
|
+
if (seed.comments)
|
|
23
|
+
this.commentsCache = [...seed.comments];
|
|
24
|
+
}
|
|
19
25
|
}
|
|
20
26
|
taskKey(task) { return `${this.repository}#${this.prNumber}:${task.jobId ?? ''}/${task.id}`; }
|
|
21
27
|
taskKeyFromBody(body) { const task = parseTask(body); return task ? this.taskKey(task) : null; }
|
package/dist/store.js
CHANGED
|
@@ -12,28 +12,39 @@ function publicationFromComments(rows, trustedAuthors) {
|
|
|
12
12
|
});
|
|
13
13
|
return allowed.map(item => parsePackagePublicationMarker(String(item.body ?? ''))).filter((item) => item !== null).at(-1) ?? null;
|
|
14
14
|
}
|
|
15
|
-
export async function
|
|
15
|
+
export async function loadManagedGitHubPullRequestSnapshotGraphql(client, repository, prNumber, trustedAuthors) {
|
|
16
16
|
const [owner, name] = repository.split('/', 2);
|
|
17
17
|
if (!owner || !name || !Number.isInteger(prNumber) || prNumber <= 0 || !client.graphql)
|
|
18
|
-
return [];
|
|
19
|
-
const query = 'query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){state isDraft merged mergedAt closedAt body comments(last:100){nodes{databaseId url author{login} body}}}}}';
|
|
18
|
+
return { overviews: [], seed: { prNode: {}, comments: null } };
|
|
19
|
+
const query = 'query($owner:String!,$name:String!,$number:Int!){repository(owner:$owner,name:$name){pullRequest(number:$number){id state isDraft merged mergedAt closedAt body headRefName headRefOid comments(last:100){totalCount nodes{id databaseId url updatedAt author{login} body}}}}}';
|
|
20
20
|
const data = await client.graphql(query, { owner, name, number: prNumber });
|
|
21
21
|
const repo = data.repository;
|
|
22
22
|
const pr = repo?.pullRequest;
|
|
23
23
|
if (!pr)
|
|
24
|
-
return [];
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
|
|
24
|
+
return { overviews: [], seed: { prNode: {}, comments: null } };
|
|
25
|
+
const commentConnection = pr.comments;
|
|
26
|
+
const commentRows = (Array.isArray(commentConnection?.nodes) ? commentConnection.nodes : []);
|
|
27
|
+
const totalCount = Number(commentConnection?.totalCount ?? commentRows.length);
|
|
28
|
+
const seedComments = Number.isFinite(totalCount) && totalCount <= commentRows.length ? commentRows.map(row => {
|
|
28
29
|
const author = row.author;
|
|
30
|
+
return {
|
|
31
|
+
id: Number(row.databaseId ?? 0),
|
|
32
|
+
nodeId: typeof row.id === 'string' ? row.id : undefined,
|
|
33
|
+
url: typeof row.url === 'string' ? row.url : undefined,
|
|
34
|
+
body: String(row.body ?? ''),
|
|
35
|
+
updatedAt: String(row.updatedAt ?? ''),
|
|
36
|
+
authorLogin: typeof author?.login === 'string' ? author.login : null,
|
|
37
|
+
};
|
|
38
|
+
}) : null;
|
|
39
|
+
const trusted = commentRows.filter(item => {
|
|
40
|
+
const author = item.author;
|
|
29
41
|
return typeof author?.login === 'string' && trustedAuthors.has(author.login);
|
|
30
42
|
});
|
|
31
43
|
const bodyJob = parseJob(typeof pr.body === 'string' ? pr.body : '');
|
|
32
44
|
const commentJobs = trusted.map(item => parseJob(String(item.body ?? ''))).filter((item) => item !== null);
|
|
33
45
|
const jobs = bodyJob ? [bodyJob] : commentJobs;
|
|
34
|
-
const publication = publicationFromComments(
|
|
35
|
-
const tasks = trusted.map(
|
|
36
|
-
const row = item;
|
|
46
|
+
const publication = publicationFromComments(commentRows, trustedAuthors);
|
|
47
|
+
const tasks = trusted.map(row => {
|
|
37
48
|
const task = parseTask(String(row.body ?? ''));
|
|
38
49
|
if (!task)
|
|
39
50
|
return null;
|
|
@@ -42,12 +53,18 @@ export async function loadManagedGitHubPullRequestGraphql(client, repository, pr
|
|
|
42
53
|
}).filter((item) => item !== null);
|
|
43
54
|
const state = String(pr.state ?? '').toUpperCase();
|
|
44
55
|
const githubState = pr.merged === true ? 'MERGED' : state === 'CLOSED' ? 'CLOSED' : 'OPEN';
|
|
45
|
-
|
|
56
|
+
const overviews = jobs.map(job => {
|
|
46
57
|
job.tasks = tasks.filter(task => task.jobId === job.id || (!task.jobId && jobs.length === 1));
|
|
47
58
|
if (!job.description && typeof pr.body === 'string' && pr.body)
|
|
48
59
|
job.description = pr.body;
|
|
60
|
+
if (typeof pr.headRefOid === 'string' && pr.headRefOid)
|
|
61
|
+
job.headSha = pr.headRefOid;
|
|
49
62
|
return { job, githubState, draft: pr.isDraft === true, mergedAt: typeof pr.mergedAt === 'string' ? pr.mergedAt : null, closedAt: typeof pr.closedAt === 'string' ? pr.closedAt : null, publication };
|
|
50
63
|
});
|
|
64
|
+
return { overviews, seed: { prNode: pr, comments: seedComments } };
|
|
65
|
+
}
|
|
66
|
+
export async function loadManagedGitHubPullRequestGraphql(client, repository, prNumber, trustedAuthors) {
|
|
67
|
+
return (await loadManagedGitHubPullRequestSnapshotGraphql(client, repository, prNumber, trustedAuthors)).overviews;
|
|
51
68
|
}
|
|
52
69
|
export async function loadManagedGitHubJobGraphql(client, repository, prNumber, jobId, trustedAuthors) {
|
|
53
70
|
return (await loadManagedGitHubPullRequestGraphql(client, repository, prNumber, trustedAuthors)).find(item => item.job.id === jobId) ?? null;
|