agents-relay 1.0.171 → 1.0.173
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 +12 -1
- package/dist/reconciler-review.js +1 -1
- package/dist/reconciler.js +22 -0
- package/dist/relayd.js +17 -10
- package/package.json +1 -1
package/dist/pool.js
CHANGED
|
@@ -176,7 +176,18 @@ export class RepositoryWorkerPool {
|
|
|
176
176
|
capacity -= allowance;
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
|
-
async reconcileKnown(discovered) {
|
|
179
|
+
async reconcileKnown(discovered) {
|
|
180
|
+
for (const item of discovered.filter(value => value.githubState === 'OPEN' && value.job.state === 'ACTIVE')) {
|
|
181
|
+
this.jobTargets.set(item.job.id, { repository: item.job.repository, prNumber: item.job.prNumber });
|
|
182
|
+
const store = new GitHubStore(this.client, item.job.repository, item.job.prNumber, this.trustedAuthors);
|
|
183
|
+
const reconciler = this.makeReconciler(store, 0);
|
|
184
|
+
if (typeof reconciler.recoverStartupOrphans === 'function')
|
|
185
|
+
await this.reconcileExclusive(item.job.id, async () => {
|
|
186
|
+
await withGitHubRequestContext(this.client, 'pool.startup-orphan-recovery', () => reconciler.recoverStartupOrphans(item.job.id));
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
await this.reconcileDiscovered(discovered);
|
|
190
|
+
}
|
|
180
191
|
async reconcile() {
|
|
181
192
|
if (this.isRateLimited())
|
|
182
193
|
return;
|
|
@@ -104,7 +104,7 @@ export class ReconcilerReviewBase extends ReconcilerRuntimeBase {
|
|
|
104
104
|
const registered = await this.store.listAgents();
|
|
105
105
|
const candidates = workerCandidates(this.options.adapters, registered, usage);
|
|
106
106
|
const runtimeSummary = this.options.runtimeSummaryProvider?.();
|
|
107
|
-
for (const task of job.tasks.filter(item => item.kind !== 'planner' && item.
|
|
107
|
+
for (const task of job.tasks.filter(item => item.kind !== 'planner' && item.adapter !== 'orchestrator' && ['QUEUED', 'BLOCKED'].includes(item.state) && item.output && !item.workerSelection && (item.workerRequirements || item.planningSource === 'model-planner'))) {
|
|
108
108
|
const requirements = task.workerRequirements ?? { capabilities: [] };
|
|
109
109
|
const taskFacts = {
|
|
110
110
|
id: task.id,
|
package/dist/reconciler.js
CHANGED
|
@@ -214,6 +214,8 @@ export class Reconciler extends ReconcilerReviewBase {
|
|
|
214
214
|
this.reconciling = false;
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
|
+
async recoverStartupOrphans(jobId) { const job = await this.store.load(jobId); if (job.state === 'ACTIVE' && (await this.store.pullRequestState?.() ?? 'OPEN') === 'OPEN')
|
|
218
|
+
await this.recoverOrphanedLocalExecutions(job); return this.store.load(jobId); }
|
|
217
219
|
async cancelActiveTasks(jobId, reason) {
|
|
218
220
|
const job = await this.store.load(jobId);
|
|
219
221
|
for (const task of job.tasks.filter(item => !['SUCCEEDED', 'FAILED', 'CANCELLED'].includes(item.state))) {
|
|
@@ -262,6 +264,26 @@ export class Reconciler extends ReconcilerReviewBase {
|
|
|
262
264
|
await this.emit(eventFor(job.id, task.id, task.parentTaskId, 'task.failed', 'failed', task.error, 'user', { reason: 'lease_expired' }));
|
|
263
265
|
}
|
|
264
266
|
}
|
|
267
|
+
async recoverOrphanedLocalExecutions(job) {
|
|
268
|
+
for (const stale of job.tasks.filter(t => t.state === 'RUNNING' && t.executionId && t.leaseOwner && t.leaseOwner !== this.options.owner && t.adapter !== 'chatgpt')) {
|
|
269
|
+
const task = (await this.store.load(job.id)).tasks.find(t => t.id === stale.id);
|
|
270
|
+
if (!task || task.state !== 'RUNNING' || task.executionId !== stale.executionId || task.leaseOwner !== stale.leaseOwner || task.leaseExpiresAt !== stale.leaseExpiresAt || task.adapter === 'chatgpt' || task.leaseOwner === this.options.owner)
|
|
271
|
+
continue;
|
|
272
|
+
await transitionTaskEvent(task, 'task.failed');
|
|
273
|
+
task.error = 'Orphaned after relayd restart';
|
|
274
|
+
task.leaseOwner = null;
|
|
275
|
+
task.leaseExpiresAt = null;
|
|
276
|
+
task.executionId = null;
|
|
277
|
+
if (task.attempt < task.maxAttempts) {
|
|
278
|
+
await transitionTaskEvent(task, 'retry');
|
|
279
|
+
await this.store.saveTask(task);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
task.error += ' after maximum attempts';
|
|
283
|
+
await this.store.saveTask(task);
|
|
284
|
+
await this.emit(eventFor(job.id, task.id, task.parentTaskId, 'task.failed', 'failed', task.error, 'user', { reason: 'startup_orphaned' }));
|
|
285
|
+
}
|
|
286
|
+
}
|
|
265
287
|
async planJob(jobId, guidance) {
|
|
266
288
|
return executeManualPlan(this.store, jobId, guidance, this.options.plannerTimeoutMs ?? storySizeTimeoutMs('M'), (job, task) => this.launchPlanner(job, task));
|
|
267
289
|
}
|
package/dist/relayd.js
CHANGED
|
@@ -120,7 +120,7 @@ export async function runDaemon(argv) {
|
|
|
120
120
|
cachedOverviews = parsed;
|
|
121
121
|
}
|
|
122
122
|
catch { /* first run or invalid cache */ }
|
|
123
|
-
const refreshAll = async (full = false) => {
|
|
123
|
+
const refreshAll = async (full = false, failOnError = false) => {
|
|
124
124
|
if (client.rateLimitStatus().limited)
|
|
125
125
|
return cachedOverviews;
|
|
126
126
|
const scanRepositories = full ? repositories : recoveryScanRepositories(repositories, cachedOverviews, dormantProbeIndex++);
|
|
@@ -139,6 +139,8 @@ export async function runDaemon(argv) {
|
|
|
139
139
|
return cachedOverviews;
|
|
140
140
|
}
|
|
141
141
|
catch (error) {
|
|
142
|
+
if (failOnError)
|
|
143
|
+
throw error;
|
|
142
144
|
process.stderr.write(`warning: workspace job refresh unavailable: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
143
145
|
return cachedOverviews;
|
|
144
146
|
}
|
|
@@ -275,7 +277,6 @@ export async function runDaemon(argv) {
|
|
|
275
277
|
taskCancel: workspaceTaskAction('cancel'),
|
|
276
278
|
taskSetState: workspaceTaskSetState,
|
|
277
279
|
});
|
|
278
|
-
await once(server, 'listening');
|
|
279
280
|
const reconcileSafely = async () => {
|
|
280
281
|
if (client.rateLimitStatus().limited)
|
|
281
282
|
return;
|
|
@@ -301,17 +302,23 @@ export async function runDaemon(argv) {
|
|
|
301
302
|
}
|
|
302
303
|
};
|
|
303
304
|
const watchdogDefault = webhook ? SERVICE_DEFAULTS.webhookWatchdogMs : SERVICE_DEFAULTS.watchdogMs;
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
305
|
+
let interval;
|
|
306
|
+
let scheduleInterval;
|
|
307
|
+
const stop = () => { if (interval)
|
|
308
|
+
clearInterval(interval); if (scheduleInterval)
|
|
309
|
+
clearInterval(scheduleInterval); void stopWatch().finally(() => server.close(() => process.exit(0))); };
|
|
309
310
|
process.once('SIGINT', stop);
|
|
310
311
|
process.once('SIGTERM', stop);
|
|
312
|
+
await once(server, 'listening');
|
|
313
|
+
const startupItems = await refreshAll(true, true); // void refreshAll(true)
|
|
314
|
+
await pool.reconcileKnown(startupItems);
|
|
315
|
+
// Startup recovery must finish before normal polling or scheduled work can run.
|
|
316
|
+
await scheduleHeartbeatSafely();
|
|
317
|
+
interval = setInterval(() => void reconcileSafely(), Number(value(argv, '--interval', String(watchdogDefault))));
|
|
318
|
+
interval.unref();
|
|
319
|
+
scheduleInterval = setInterval(() => void scheduleHeartbeatSafely(), SCHEDULE_HEARTBEAT_MS);
|
|
320
|
+
scheduleInterval.unref();
|
|
311
321
|
console.log('Dashboard listening; repository worker pool active');
|
|
312
|
-
void refreshAll(true)
|
|
313
|
-
.then(async (items) => { await scheduleHeartbeatSafely(); await pool.reconcileKnown(items); })
|
|
314
|
-
.catch(error => process.stderr.write(`warning: startup reconciliation unavailable: ${error instanceof Error ? error.message : String(error)}\n`));
|
|
315
322
|
}
|
|
316
323
|
if (isEntrypoint(import.meta.url))
|
|
317
324
|
runDaemon(process.argv.slice(2)).catch(error => { console.error(error instanceof Error ? error.message : String(error)); process.exitCode = 1; });
|