agents-relay 1.0.138 → 1.0.140
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/dashboard-html.js +1 -1
- package/dist/dashboard.js +1 -1
- package/dist/pool.js +11 -2
- package/dist/relayd.js +33 -13
- package/dist/store-github.js +7 -1
- package/dist/store.js +28 -11
- package/package.json +1 -1
package/dist/dashboard-html.js
CHANGED
|
@@ -373,7 +373,7 @@ function formatCredit(value,currency){if(value===null||value===undefined||!Numbe
|
|
|
373
373
|
function creditProvider(provider){const row=element('section','usage-provider');row.append(element('h3','',text(provider.label)));const credits=provider.credits||{};if(!credits.available||!Array.isArray(credits.balances)||!credits.balances.length){row.append(element('p','empty',text(credits.reason||'Credit balance is unavailable.')));return row;}const balance=credits.balances[0];row.append(element('div','detail-value',text(formatCredit(balance.remaining,balance.currency))));return row;}
|
|
374
374
|
function githubBucket(label,bucket){const row=element('div','usage-row');const heading=element('div','usage-label');heading.append(element('span','',text(label)),element('span','',text(bucket.remaining+' remaining')));row.append(heading);row.append(element('div','subtle',text(bucket.used+' used / '+bucket.limit+' limit')));const reset=resetText({resetsAt:bucket.reset});if(reset)row.append(element('div','subtle',text(reset)));return row;}
|
|
375
375
|
function githubCounterLine(label,values){const entries=Object.entries(values||{}).sort((a,b)=>Number(b[1])-Number(a[1])).slice(0,8);return entries.length?element('div','subtle',text(label+' · '+entries.map(([key,count])=>key+' '+count).join(' · '))):null;}
|
|
376
|
-
function githubUsageCard(snapshot,status){const card=element('section','usage-provider');card.append(element('h3','',text('GitHub API')));const resources=snapshot?.resources||{};const bars=element('div','usage-bars');if(resources.core)bars.append(githubBucket('REST core',resources.core));if(resources.graphql)bars.append(githubBucket('GraphQL',resources.graphql));if(resources.search)bars.append(githubBucket('Search',resources.search));if(!bars.children.length)bars.append(element('p','empty',text('GitHub quota telemetry is unavailable.')));if(status?.limited)bars.append(element('div','error-state',text('Relay cooldown active'+(status.cooldownUntil?' until '+new Date(status.cooldownUntil).toLocaleString():''))));else bars.append(element('div','subtle',text('Relay cooldown inactive')));if(status?.calls)bars.append(element('div','subtle',text('Calls since start · GraphQL '+(status.calls.graphql||0)+' · REST '+(status.calls.rest||0)+' · quota '+(status.calls.rateLimit||0))));const operations=githubCounterLine('GraphQL operations',status?.graphql?.operations);if(operations)bars.append(operations);const contexts=githubCounterLine('GraphQL contexts',status?.graphql?.contexts);if(contexts)bars.append(contexts);if(status?.readPaths)bars.append(element('div','subtle',text('Read paths · workspace scans '+(status.readPaths.workspaceScans||0)+' · PR refreshes '+(status.readPaths.prRefreshes||0))));card.append(bars);return card;}
|
|
376
|
+
function githubUsageCard(snapshot,status){const card=element('section','usage-provider');card.append(element('h3','',text('GitHub API')));const resources=snapshot?.resources||{};const bars=element('div','usage-bars');if(resources.core)bars.append(githubBucket('REST core',resources.core));if(resources.graphql)bars.append(githubBucket('GraphQL',resources.graphql));if(resources.search)bars.append(githubBucket('Search',resources.search));if(!bars.children.length)bars.append(element('p','empty',text('GitHub quota telemetry is unavailable.')));if(status?.limited)bars.append(element('div','error-state',text('Relay cooldown active'+(status.cooldownUntil?' until '+new Date(status.cooldownUntil).toLocaleString():''))));else bars.append(element('div','subtle',text('Relay cooldown inactive')));if(status?.calls)bars.append(element('div','subtle',text('Calls since start · GraphQL '+(status.calls.graphql||0)+' · REST '+(status.calls.rest||0)+' · quota '+(status.calls.rateLimit||0))));const operations=githubCounterLine('GraphQL operations',status?.graphql?.operations);if(operations)bars.append(operations);const contexts=githubCounterLine('GraphQL contexts',status?.graphql?.contexts);if(contexts)bars.append(contexts);if(status?.readPaths){bars.append(element('div','subtle',text('Read paths · workspace scans '+(status.readPaths.workspaceScans||0)+' · PR refreshes '+(status.readPaths.prRefreshes||0)+' · coalesced '+(status.readPaths.prRefreshCoalesced||0))));const triggers=githubCounterLine('PR refresh triggers',status.readPaths.prRefreshesByTrigger);if(triggers)bars.append(triggers);const coalesced=githubCounterLine('Coalesced refresh triggers',status.readPaths.prRefreshCoalescedByTrigger);if(coalesced)bars.append(coalesced);}card.append(bars);return card;}
|
|
377
377
|
async function drawUsage(){try{const overview=await loadJSON('/api/usage');usageRoot.replaceChildren();usageMeta.textContent='Real account telemetry when available';const providers=Array.isArray(overview.providers)?overview.providers:[];const credits=Array.isArray(overview.credits)?overview.credits:[];const grid=element('div','usage-providers');if(overview.github||overview.githubStatus)grid.append(githubUsageCard(overview.github,overview.githubStatus));for(const provider of providers)grid.append(usageProvider(provider));for(const provider of credits)grid.append(creditProvider(provider));if(!grid.children.length){usageRoot.append(element('p','empty',text('Usage telemetry is unavailable.')));return;}usageRoot.append(grid);}catch(error){usageMeta.textContent='Unavailable';usageRoot.replaceChildren(element('p','error-state',text('Usage unavailable: '+error.message)));}}
|
|
378
378
|
async function drawRules(){rulesStatus.textContent='';try{const rules=await loadJSON('/api/model-router-rules');rulesText.value=typeof rules.text==='string'?rules.text:'';rulesMeta.textContent=rules.hash&&rules.hash!=='none'?'Loaded · '+rules.hash.slice(0,12):'No custom rules saved';}catch(error){rulesMeta.textContent='Unavailable';rulesStatus.textContent='Could not load rules: '+error.message;}}
|
|
379
379
|
async function saveRules(){rulesSave.disabled=true;rulesStatus.textContent='Saving…';try{const response=await fetch('/api/model-router-rules',{method:'PUT',headers:{'content-type':'text/plain; charset=utf-8'},body:rulesText.value});if(!response.ok)throw new Error(response.status+' '+await response.text());const rules=await response.json();rulesMeta.textContent='Saved · '+rules.hash.slice(0,12);rulesStatus.textContent='Saved. New routing decisions will load these rules.';}catch(error){rulesStatus.textContent='Save failed: '+error.message;}finally{rulesSave.disabled=false;}}
|
package/dist/dashboard.js
CHANGED
|
@@ -68,7 +68,7 @@ export function serveDashboard(store, port = 8787, bus, refreshMs = 300000, jobI
|
|
|
68
68
|
const discoveredJobs = async (forceRefresh = false) => {
|
|
69
69
|
try {
|
|
70
70
|
const jobs = overviewProvider
|
|
71
|
-
? (await overviewProvider(forceRefresh)).map(item => ({ ...item.job, githubState: item.githubState, runtimeStatus: deriveJobRuntimeStatus(item.job, item.githubState), mergedAt: item.mergedAt, closedAt: item.closedAt, draft: item.draft, publication: item.publication }))
|
|
71
|
+
? (await overviewProvider(forceRefresh, forceRefresh ? 'dashboard.jobs-refresh' : 'internal.unknown')).map(item => ({ ...item.job, githubState: item.githubState, runtimeStatus: deriveJobRuntimeStatus(item.job, item.githubState), mergedAt: item.mergedAt, closedAt: item.closedAt, draft: item.draft, publication: item.publication }))
|
|
72
72
|
: jobId ? [await store.load(jobId)] : [];
|
|
73
73
|
lastKnownJobs = jobs;
|
|
74
74
|
return jobs;
|
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';
|
|
@@ -19,6 +19,10 @@ import { deriveJobRuntimeStatus } from './runtime-status.js';
|
|
|
19
19
|
import { decisionMode, LayaCliDecisionEngine } from './decision.js';
|
|
20
20
|
import { JsonlDecisionAuditStore } from './decision-audit.js';
|
|
21
21
|
import { summarizeRuntime } from './runtime-summary.js';
|
|
22
|
+
const PR_REFRESH_COALESCE_MS = 1000;
|
|
23
|
+
export function shouldCoalescePullRequestRefresh(completedAt, now = Date.now(), force = false) {
|
|
24
|
+
return !force && completedAt !== null && now >= completedAt && now - completedAt < PR_REFRESH_COALESCE_MS;
|
|
25
|
+
}
|
|
22
26
|
export const daemonHelp = `Agents Relay daemon runs the dashboard and worker pool.
|
|
23
27
|
|
|
24
28
|
Usage:
|
|
@@ -97,7 +101,7 @@ export async function runDaemon(argv) {
|
|
|
97
101
|
const usageRegistry = codexAndZaiUsageRegistry();
|
|
98
102
|
const decisionAuditStore = new JsonlDecisionAuditStore(process.env.AGENTS_RELAY_DECISION_AUDIT_PATH ?? join(homedir(), '.agents-relay', 'decision-audit.jsonl'));
|
|
99
103
|
let cachedOverviews = [];
|
|
100
|
-
const githubReadPaths = { workspaceScans: 0, prRefreshes: 0 };
|
|
104
|
+
const githubReadPaths = { workspaceScans: 0, prRefreshes: 0, prRefreshesByTrigger: {}, prRefreshCoalesced: 0, prRefreshCoalescedByTrigger: {} };
|
|
101
105
|
let dormantProbeIndex = 0;
|
|
102
106
|
const makeReconciler = (store, maxConcurrent) => new Reconciler(store, { owner: `relayd-${process.pid}`, maxConcurrent, leaseMs: Number(value(argv, '--lease-ms', '300000')), adapters: [new CodexAdapter(value(argv, '--codex', 'codex')), new ChatGptAdapter()], continuations: [new CodexThreadContinuation(value(argv, '--codex', 'codex')), new CommandContinuation(), new WebhookContinuation()], planner: runtimePlanner(argv), usageSnapshotProvider: () => usageRegistry.snapshot(), runtimeSummaryProvider: () => ({ chatgptRunning: summarizeRuntime(cachedOverviews.map(item => item.job)).workers.chatgpt }), decisionEngine: new LayaCliDecisionEngine(), decisionMode: decisionMode(), decisionAuditStore, eventBus: bus, reviewer: runtimeReviewer(), projectPathForJob: async (job) => resolveRepositoryProjectPath(repositoryPaths.get(job.repository) ?? await resolveManagedProjectPath(job.repository, explicitProjectPath, workspaceRoot), job.projectPath) });
|
|
103
107
|
if (repositories.length === 0)
|
|
@@ -134,7 +138,7 @@ export async function runDaemon(argv) {
|
|
|
134
138
|
return cachedOverviews;
|
|
135
139
|
}
|
|
136
140
|
};
|
|
137
|
-
const overviews = async (forceRefresh = false) => forceRefresh ? refreshAll(true) : cachedOverviews;
|
|
141
|
+
const overviews = async (forceRefresh = false, _trigger = 'internal.unknown') => forceRefresh ? refreshAll(true) : cachedOverviews;
|
|
138
142
|
const persistCache = async () => {
|
|
139
143
|
try {
|
|
140
144
|
await mkdir(cacheDirectory, { recursive: true });
|
|
@@ -143,21 +147,34 @@ export async function runDaemon(argv) {
|
|
|
143
147
|
catch { /* cache is best effort */ }
|
|
144
148
|
};
|
|
145
149
|
const pullRequestRefreshFlights = new Map();
|
|
146
|
-
const
|
|
150
|
+
const pullRequestRefreshCache = new Map();
|
|
151
|
+
const incrementTrigger = (counts, trigger) => { counts[trigger] = (counts[trigger] ?? 0) + 1; };
|
|
152
|
+
const refreshPullRequestSnapshot = async (repository, prNumber, trigger = 'internal', options = {}) => {
|
|
147
153
|
if (client.rateLimitStatus().limited)
|
|
148
|
-
return cachedOverviews.filter(item => item.job.repository === repository && item.job.prNumber === prNumber);
|
|
154
|
+
return { overviews: cachedOverviews.filter(item => item.job.repository === repository && item.job.prNumber === prNumber), seed: { prNode: {}, comments: null } };
|
|
149
155
|
const key = `${repository}#${prNumber}`;
|
|
156
|
+
incrementTrigger(githubReadPaths.prRefreshesByTrigger, trigger);
|
|
150
157
|
const existing = pullRequestRefreshFlights.get(key);
|
|
151
|
-
if (existing)
|
|
158
|
+
if (existing) {
|
|
159
|
+
githubReadPaths.prRefreshCoalesced += 1;
|
|
160
|
+
incrementTrigger(githubReadPaths.prRefreshCoalescedByTrigger, trigger);
|
|
152
161
|
return existing;
|
|
162
|
+
}
|
|
163
|
+
const cached = pullRequestRefreshCache.get(key);
|
|
164
|
+
if (cached && shouldCoalescePullRequestRefresh(cached.completedAt, Date.now(), options.force === true)) {
|
|
165
|
+
githubReadPaths.prRefreshCoalesced += 1;
|
|
166
|
+
incrementTrigger(githubReadPaths.prRefreshCoalescedByTrigger, trigger);
|
|
167
|
+
return cached.snapshot;
|
|
168
|
+
}
|
|
153
169
|
const flight = (async () => {
|
|
154
170
|
githubReadPaths.prRefreshes += 1;
|
|
155
|
-
const
|
|
171
|
+
const snapshot = await withGitHubRequestContext(client, `relayd.pr-refresh:${trigger}`, () => loadManagedGitHubPullRequestSnapshotGraphql(client, repository, prNumber, trusted));
|
|
156
172
|
cachedOverviews = cachedOverviews.filter(item => item.job.repository !== repository || item.job.prNumber !== prNumber);
|
|
157
|
-
cachedOverviews.push(...
|
|
173
|
+
cachedOverviews.push(...snapshot.overviews);
|
|
158
174
|
cachedOverviews = dashboardManagedJobs(cachedOverviews);
|
|
159
175
|
await persistCache();
|
|
160
|
-
|
|
176
|
+
pullRequestRefreshCache.set(key, { snapshot, completedAt: Date.now() });
|
|
177
|
+
return snapshot;
|
|
161
178
|
})();
|
|
162
179
|
pullRequestRefreshFlights.set(key, flight);
|
|
163
180
|
try {
|
|
@@ -168,6 +185,7 @@ export async function runDaemon(argv) {
|
|
|
168
185
|
pullRequestRefreshFlights.delete(key);
|
|
169
186
|
}
|
|
170
187
|
};
|
|
188
|
+
const refreshPullRequest = async (repository, prNumber, trigger, options) => (await refreshPullRequestSnapshot(repository, prNumber, trigger, options)).overviews;
|
|
171
189
|
const initial = cachedOverviews;
|
|
172
190
|
const fallback = initial[0]?.job;
|
|
173
191
|
const dashboardStore = fallback ? new GitHubStore(client, fallback.repository, fallback.prNumber, trusted) : new InMemoryStore();
|
|
@@ -195,7 +213,7 @@ export async function runDaemon(argv) {
|
|
|
195
213
|
throw new Error(`Managed job ${jobId} not found`);
|
|
196
214
|
const store = new GitHubStore(client, overview.job.repository, overview.job.prNumber, trusted);
|
|
197
215
|
const result = await dashboardTaskSetState(store)(jobId, taskId, state);
|
|
198
|
-
await refreshPullRequest(overview.job.repository, overview.job.prNumber);
|
|
216
|
+
await refreshPullRequest(overview.job.repository, overview.job.prNumber, 'dashboard.task-state', { force: true });
|
|
199
217
|
return result;
|
|
200
218
|
};
|
|
201
219
|
const secretFile = value(argv, '--github-webhook-secret-file');
|
|
@@ -206,8 +224,10 @@ export async function runDaemon(argv) {
|
|
|
206
224
|
refresh: async (event) => {
|
|
207
225
|
if (!event.repository || !event.pullRequest)
|
|
208
226
|
return [];
|
|
209
|
-
const
|
|
210
|
-
|
|
227
|
+
const snapshot = await refreshPullRequestSnapshot(event.repository, event.pullRequest, `github.webhook:${event.event}${event.action ? ':' + event.action : ''}`);
|
|
228
|
+
for (const item of snapshot.overviews)
|
|
229
|
+
pool.seedTargetSnapshot(event.repository, event.pullRequest, item.job.id, snapshot.seed);
|
|
230
|
+
return snapshot.overviews.map(item => item.job.id);
|
|
211
231
|
},
|
|
212
232
|
wake: async (event) => {
|
|
213
233
|
if (!event.repository || !event.pullRequest)
|
|
@@ -230,7 +250,7 @@ export async function runDaemon(argv) {
|
|
|
230
250
|
if (client.rateLimitStatus().limited)
|
|
231
251
|
return cachedJob();
|
|
232
252
|
try {
|
|
233
|
-
const refreshed = (await refreshPullRequest(cached.job.repository, cached.job.prNumber)).find(item => item.job.id === jobId);
|
|
253
|
+
const refreshed = (await refreshPullRequest(cached.job.repository, cached.job.prNumber, 'dashboard.selected-job', { force: true })).find(item => item.job.id === jobId);
|
|
234
254
|
if (!refreshed)
|
|
235
255
|
return null;
|
|
236
256
|
return { ...refreshed.job, githubState: refreshed.githubState, runtimeStatus: deriveJobRuntimeStatus(refreshed.job, refreshed.githubState), mergedAt: refreshed.mergedAt, closedAt: refreshed.closedAt, draft: refreshed.draft, publication: refreshed.publication };
|
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;
|