agents-relay 1.0.107 → 1.0.109
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/cli-support.js +1 -1
- package/dist/dashboard-html.js +34 -9
- package/dist/pool.js +27 -5
- package/dist/relayd.js +4 -0
- package/dist/worker-routing.js +1 -1
- package/package.json +1 -1
package/dist/cli-support.js
CHANGED
|
@@ -3,7 +3,7 @@ import { InMemoryStore, GitHubStore } from './store.js';
|
|
|
3
3
|
import { githubAuthContext } from './github-auth.js';
|
|
4
4
|
import { NatsEventBus, eventFor } from './events.js';
|
|
5
5
|
import { parseJobSchedule } from './scheduler.js';
|
|
6
|
-
export const SERVICE_DEFAULTS = { port: 8787, watchdogMs: 300000, webhookWatchdogMs:
|
|
6
|
+
export const SERVICE_DEFAULTS = { port: 8787, watchdogMs: 300000, webhookWatchdogMs: 300000, dashboardRefreshMs: 300000 };
|
|
7
7
|
export class ServiceEventBus {
|
|
8
8
|
upstream;
|
|
9
9
|
unsubscribers = new Set();
|
package/dist/dashboard-html.js
CHANGED
|
@@ -73,11 +73,13 @@ export const dashboardHtml = `<!doctype html>
|
|
|
73
73
|
.detail-label { margin:0; color:var(--muted); font-size:.66rem; text-transform:uppercase; letter-spacing:.04em; }
|
|
74
74
|
.detail-value { margin:.12rem 0 0; font-weight:650; overflow-wrap:anywhere; }
|
|
75
75
|
.section-title { margin:.8rem 0 .35rem; color:var(--muted); font-size:.72rem; letter-spacing:.05em; text-transform:uppercase; }
|
|
76
|
-
ul.tree { display:grid; gap:.25rem; }
|
|
77
|
-
ul.tree
|
|
78
|
-
.
|
|
76
|
+
ul.tree { display:grid; gap:.25rem; min-width:0; max-width:100%; }
|
|
77
|
+
ul.tree li { min-width:0; max-width:100%; }
|
|
78
|
+
ul.tree ul { width:calc(100% - .6rem); min-width:0; max-width:calc(100% - .6rem); list-style:none; margin:.2rem 0 .1rem .6rem; padding-left:.6rem; border-left:1px solid var(--border); }
|
|
79
|
+
.task-row { width:100%; max-width:100%; overflow:hidden; display:flex; align-items:center; gap:.4rem; min-width:0; padding:.32rem .38rem; border:0; border-radius:.35rem; background:transparent; color:inherit; font:inherit; text-align:left; cursor:pointer; }
|
|
79
80
|
.task-row[aria-current="true"] { background:var(--accent2); }
|
|
80
|
-
.task-
|
|
81
|
+
.task-row .badge { flex:0 0 auto; }
|
|
82
|
+
.task-id { min-width:0; max-width:100%; flex:1 1 0; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; font: .77rem ui-monospace,SFMono-Regular,Menlo,monospace; }
|
|
81
83
|
.task-actions { display:flex; justify-content:flex-end; gap:.25rem; margin-bottom:.5rem; }
|
|
82
84
|
.task-link { color:var(--accent); font-weight:700; }
|
|
83
85
|
.agent-graph { display:grid; gap:.45rem; margin-top:.55rem; padding:.55rem; border:1px solid var(--border); border-radius:.4rem; background:var(--surface2); overflow:auto; }
|
|
@@ -120,11 +122,34 @@ export const dashboardHtml = `<!doctype html>
|
|
|
120
122
|
.mobile-only { display:inline-flex; }
|
|
121
123
|
#jobs-view .panel-head { display:grid; align-items:start; }
|
|
122
124
|
#jobs-view .panel-actions { width:100%; flex-wrap:wrap; }
|
|
123
|
-
|
|
124
|
-
#jobs-view .job-
|
|
125
|
+
.runtime-summary { display:none; }
|
|
126
|
+
#jobs-view .job-filters { width:auto; }
|
|
127
|
+
#jobs-view .job-search,
|
|
128
|
+
#jobs-view #quick-status,
|
|
129
|
+
#jobs-view #quick-autonomous,
|
|
130
|
+
#jobs-view #quick-draft { display:none; }
|
|
125
131
|
#jobs-view .panel-body { max-height:none; }
|
|
126
132
|
#jobs-workspace { gap:.65rem; }
|
|
127
133
|
#jobs-workspace .panel-body { max-height:none; }
|
|
134
|
+
#usage-view .panel-head { padding:.6rem .7rem .35rem; }
|
|
135
|
+
#usage-view .panel-head .subtle { display:none; }
|
|
136
|
+
#usage-view .panel-body { padding:.45rem .5rem .65rem; max-height:none; }
|
|
137
|
+
#usage-view .usage-providers { gap:.45rem; }
|
|
138
|
+
#usage-view .usage-provider {
|
|
139
|
+
grid-template-columns:1fr;
|
|
140
|
+
gap:.55rem;
|
|
141
|
+
padding:.65rem .7rem;
|
|
142
|
+
border-radius:.55rem;
|
|
143
|
+
background:var(--surface);
|
|
144
|
+
}
|
|
145
|
+
#usage-view .usage-provider h3 { font-size:.78rem; color:var(--muted); text-transform:uppercase; letter-spacing:.04em; }
|
|
146
|
+
#usage-view .usage-bars { gap:.7rem; }
|
|
147
|
+
#usage-view .usage-row { gap:.25rem; }
|
|
148
|
+
#usage-view .usage-label { align-items:baseline; gap:.5rem; font-size:.78rem; }
|
|
149
|
+
#usage-view .usage-label span:last-child { font-size:1.05rem; font-weight:800; color:var(--text); }
|
|
150
|
+
#usage-view .usage-track { height:.48rem; border:0; background:var(--border); }
|
|
151
|
+
#usage-view .usage-row .subtle { font-size:.68rem; line-height:1.25; }
|
|
152
|
+
#usage-view .usage-provider > .detail-value { font-size:1.2rem; font-weight:800; }
|
|
128
153
|
.job-summary,.detail-groups { grid-template-columns:1fr; }
|
|
129
154
|
.panel-head { padding:.55rem .6rem; }
|
|
130
155
|
.panel-body { padding:.6rem; }
|
|
@@ -141,8 +166,8 @@ export const dashboardHtml = `<!doctype html>
|
|
|
141
166
|
<nav role="tablist" aria-label="Console views">
|
|
142
167
|
<button id="jobs-tab" type="button" role="tab" aria-selected="true" aria-controls="jobs-view">Jobs</button>
|
|
143
168
|
<button id="usage-tab" type="button" role="tab" aria-selected="false" aria-controls="usage-view">Usage</button>
|
|
144
|
-
<button id="rules-tab" type="button" role="tab" aria-selected="false" aria-controls="rules-view">Model
|
|
145
|
-
<button id="worker-rules-tab" type="button" role="tab" aria-selected="false" aria-controls="worker-rules-view">Worker
|
|
169
|
+
<button id="rules-tab" type="button" role="tab" aria-selected="false" aria-controls="rules-view">Model Rules</button>
|
|
170
|
+
<button id="worker-rules-tab" type="button" role="tab" aria-selected="false" aria-controls="worker-rules-view">Worker Rules</button>
|
|
146
171
|
</nav>
|
|
147
172
|
</header>
|
|
148
173
|
<main>
|
|
@@ -267,7 +292,7 @@ function element(name,className,child){const node=document.createElement(name);i
|
|
|
267
292
|
function field(label,input){const box=element('div','detail-group');box.append(element('p','detail-label',text(label)),element('p','detail-value',text(input)));return box;}
|
|
268
293
|
function markdownField(label,input){const box=element('div','detail-group');box.append(element('p','detail-label',text(label)),markdownValue(input));return box;}
|
|
269
294
|
function rememberSelection(){if(selectedJob)sessionStorage.setItem('agents-relay:selected-job',selectedJob);else sessionStorage.removeItem('agents-relay:selected-job');if(selectedTaskId)sessionStorage.setItem('agents-relay:selected-task',selectedTaskId);else sessionStorage.removeItem('agents-relay:selected-task');}
|
|
270
|
-
function syncMobileJobsView(){const detail=mobileJobs.matches&&Boolean(selectedJob)
|
|
295
|
+
function syncMobileJobsView(){const detail=mobileJobs.matches&&Boolean(selectedJob)&&location.hash.startsWith('#/jobs/');document.body.dataset.mobileJobsView=detail?'detail':'list';}
|
|
271
296
|
function selected(){return jobs.find(job=>job.id===selectedJob)||jobs[0]||null;}
|
|
272
297
|
function counts(tasks){const out={};for(const task of Array.isArray(tasks)?tasks:[])out[task.state]=(out[task.state]||0)+1;return out;}
|
|
273
298
|
function compactCounts(input){const order=['RUNNING','BLOCKED','FAILED','QUEUED','DRAFT','SUCCEEDED','CANCELLED'];return order.filter(state=>input[state]).map(state=>input[state]+' '+state).join(', ')||'No tasks';}
|
package/dist/pool.js
CHANGED
|
@@ -24,6 +24,7 @@ export class RepositoryWorkerPool {
|
|
|
24
24
|
makeReconciler;
|
|
25
25
|
running = false;
|
|
26
26
|
pending = false;
|
|
27
|
+
reconcileLocks = new Map();
|
|
27
28
|
jobTargets = new Map();
|
|
28
29
|
constructor(client, repository, trustedAuthors, concurrency, makeReconciler) {
|
|
29
30
|
this.client = client;
|
|
@@ -33,14 +34,31 @@ export class RepositoryWorkerPool {
|
|
|
33
34
|
this.repositories = [...new Set(typeof repository === 'string' ? [repository] : repository)];
|
|
34
35
|
}
|
|
35
36
|
repositories;
|
|
37
|
+
async reconcileExclusive(jobId, work) {
|
|
38
|
+
while (this.reconcileLocks.has(jobId))
|
|
39
|
+
await this.reconcileLocks.get(jobId);
|
|
40
|
+
let release;
|
|
41
|
+
const gate = new Promise(resolve => { release = resolve; });
|
|
42
|
+
this.reconcileLocks.set(jobId, gate);
|
|
43
|
+
try {
|
|
44
|
+
await work();
|
|
45
|
+
}
|
|
46
|
+
finally {
|
|
47
|
+
if (this.reconcileLocks.get(jobId) === gate)
|
|
48
|
+
this.reconcileLocks.delete(jobId);
|
|
49
|
+
release();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
36
52
|
async reconcileDiscovered(discovered) {
|
|
37
53
|
for (const item of discovered)
|
|
38
54
|
this.jobTargets.set(item.job.id, { repository: item.job.repository, prNumber: item.job.prNumber });
|
|
39
55
|
// Terminal PRs must be reconciled before OPEN-job scheduling. Otherwise the
|
|
40
56
|
// OPEN-only runnable filter can strand durable jobs after GitHub merges/closes them.
|
|
41
57
|
for (const job of terminalManagedJobs(discovered)) {
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
await this.reconcileExclusive(job.id, async () => {
|
|
59
|
+
const store = new GitHubStore(this.client, job.repository, job.prNumber, this.trustedAuthors);
|
|
60
|
+
await this.makeReconciler(store, 0).reconcile(job.id);
|
|
61
|
+
});
|
|
44
62
|
}
|
|
45
63
|
const jobs = runnableManagedJobs(discovered);
|
|
46
64
|
let capacity = Math.max(1, this.concurrency);
|
|
@@ -53,7 +71,9 @@ export class RepositoryWorkerPool {
|
|
|
53
71
|
if (!needsLifecycleReconciliation(job))
|
|
54
72
|
continue;
|
|
55
73
|
const allowance = Math.max(1, Math.min(capacity, ready || (expiredRunning ? 1 : 0) || 1));
|
|
56
|
-
await this.
|
|
74
|
+
await this.reconcileExclusive(job.id, async () => {
|
|
75
|
+
await this.makeReconciler(store, allowance).reconcile(job.id);
|
|
76
|
+
});
|
|
57
77
|
capacity -= allowance;
|
|
58
78
|
}
|
|
59
79
|
}
|
|
@@ -78,8 +98,10 @@ export class RepositoryWorkerPool {
|
|
|
78
98
|
if (!this.repositories.includes(repository))
|
|
79
99
|
return;
|
|
80
100
|
this.jobTargets.set(jobId, { repository, prNumber });
|
|
81
|
-
|
|
82
|
-
|
|
101
|
+
await this.reconcileExclusive(jobId, async () => {
|
|
102
|
+
const store = new GitHubStore(this.client, repository, prNumber, this.trustedAuthors);
|
|
103
|
+
await this.makeReconciler(store, Math.max(1, this.concurrency)).reconcile(jobId);
|
|
104
|
+
});
|
|
83
105
|
}
|
|
84
106
|
async handleLifecycleEvent(event) {
|
|
85
107
|
const target = this.jobTargets.get(event.job_id);
|
package/dist/relayd.js
CHANGED
|
@@ -129,6 +129,10 @@ export async function runDaemon(argv) {
|
|
|
129
129
|
if (!overview)
|
|
130
130
|
throw new Error(`Managed job ${jobId} not found`);
|
|
131
131
|
const store = new GitHubStore(client, overview.job.repository, overview.job.prNumber, trusted);
|
|
132
|
+
if (action === 'reconcile') {
|
|
133
|
+
await pool.reconcileTarget(overview.job.repository, overview.job.prNumber, jobId);
|
|
134
|
+
return await store.load(jobId);
|
|
135
|
+
}
|
|
132
136
|
return await dashboardJobAction(store, makeReconciler(store, concurrency), action)(jobId);
|
|
133
137
|
};
|
|
134
138
|
const workspaceTaskAction = (action) => async (jobId, taskId) => {
|
package/dist/worker-routing.js
CHANGED
|
@@ -38,7 +38,7 @@ export function usageForProvider(snapshot, provider, now = Date.now()) {
|
|
|
38
38
|
};
|
|
39
39
|
}
|
|
40
40
|
const credits = snapshot?.credits.find(item => item.id === provider)?.credits;
|
|
41
|
-
if (credits
|
|
41
|
+
if (credits) {
|
|
42
42
|
const knownRemaining = credits.balances.map(item => item.remaining).filter((value) => typeof value === 'number' && Number.isFinite(value));
|
|
43
43
|
if (knownRemaining.length > 0)
|
|
44
44
|
return { healthy: knownRemaining.some(value => value > 0) };
|