agents-relay 2.0.163 → 2.0.165
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
CHANGED
|
@@ -6,6 +6,7 @@ import { ReconcileCoordinator } from './reconcile-coordinator.js';
|
|
|
6
6
|
import { errorMessage, observability, withObservabilityCorrelation } from './observability.js';
|
|
7
7
|
import { jobHasActiveExecution } from './job-execution-slot.js';
|
|
8
8
|
import { unblockNeedsNewPlan } from './unblock.js';
|
|
9
|
+
import { pendingTroubleshooterRetries } from './troubleshooter.js';
|
|
9
10
|
const RANK = { P0: 0, P1: 1, P2: 2, P3: 3 };
|
|
10
11
|
export function compareJobPriority(a, b) { return RANK[a.priority ?? 'P2'] - RANK[b.priority ?? 'P2'] || a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id); }
|
|
11
12
|
export function runnableManagedJobs(items) { return items.filter(item => item.job.state === 'ACTIVE' || item.job.projectionRecovery?.status === 'repairing').map(item => item.job).sort(compareJobPriority); }
|
|
@@ -20,6 +21,8 @@ export function needsLifecycleReconciliation(job, now = Date.now()) {
|
|
|
20
21
|
return true;
|
|
21
22
|
if (job.executionMode === 'autonomous' && unblockNeedsNewPlan(job))
|
|
22
23
|
return true;
|
|
24
|
+
if (pendingTroubleshooterRetries(job).length > 0)
|
|
25
|
+
return true;
|
|
23
26
|
if (job.tasks.some(task => (task.state === 'QUEUED' && task.executionId === null)
|
|
24
27
|
|| (task.state === 'RUNNING' && task.adapter === 'orchestrator' && task.executionId === null)
|
|
25
28
|
|| (task.state === 'DRAFT' && task.dependencies.length > 0)))
|
package/dist/reconciler.js
CHANGED
|
@@ -26,7 +26,7 @@ import { executeManualPlan } from './manual-plan.js';
|
|
|
26
26
|
import { executionEventSource, withExecutionBoundary } from './reconciler-runtime.js';
|
|
27
27
|
export { executionEventSource } from './reconciler-runtime.js';
|
|
28
28
|
import { CallbackWorkflowEffectHandler } from './workflow/effects.js';
|
|
29
|
-
import { exhaustedTroubleshooterRecoveries, pendingTroubleshooterTasks, pendingTroubleshooterReopens, refreshTroubleshooterInput } from './troubleshooter.js';
|
|
29
|
+
import { exhaustedTroubleshooterRecoveries, pendingTroubleshooterTasks, pendingTroubleshooterReopens, pendingTroubleshooterRetries, refreshTroubleshooterInput } from './troubleshooter.js';
|
|
30
30
|
import { fallbackFailedTroubleshooters, escalateExhaustedTroubleshooters } from './troubleshooter-fallback.js';
|
|
31
31
|
import { taskUsesCanonicalProjectRun } from './workspace.js';
|
|
32
32
|
import { cleanupMergedJobWorktree } from './job-worktree-cleanup.js';
|
|
@@ -142,12 +142,7 @@ export class Reconciler extends ReconcilerReviewBase {
|
|
|
142
142
|
}
|
|
143
143
|
{
|
|
144
144
|
const recoveryJob = await this.store.load(jobId);
|
|
145
|
-
for (const recovery of recoveryJob
|
|
146
|
-
if (recovery.agentName !== 'troubleshooter' || recovery.state !== 'FAILED' || recovery.attempt >= recovery.maxAttempts)
|
|
147
|
-
continue;
|
|
148
|
-
const original = recoveryJob.tasks.find(task => task.id === recovery.parentTaskId);
|
|
149
|
-
if (!original || !['FAILED', 'BLOCKED'].includes(original.state))
|
|
150
|
-
continue;
|
|
145
|
+
for (const { original, recovery } of pendingTroubleshooterRetries(recoveryJob)) {
|
|
151
146
|
await applyTaskRetryAction(recovery);
|
|
152
147
|
recovery.routing = undefined;
|
|
153
148
|
recovery.workerSelection = undefined;
|
package/dist/troubleshooter.js
CHANGED
|
@@ -65,6 +65,14 @@ export function pendingTroubleshooterReopens(job) {
|
|
|
65
65
|
}
|
|
66
66
|
return pending;
|
|
67
67
|
}
|
|
68
|
+
export function pendingTroubleshooterRetries(job) {
|
|
69
|
+
return job.tasks.flatMap(recovery => {
|
|
70
|
+
if (recovery.agentName !== 'troubleshooter' || recovery.state !== 'FAILED' || recovery.attempt >= recovery.maxAttempts)
|
|
71
|
+
return [];
|
|
72
|
+
const original = job.tasks.find(task => task.id === recovery.parentTaskId);
|
|
73
|
+
return original && ['FAILED', 'BLOCKED'].includes(original.state) ? [{ original, recovery }] : [];
|
|
74
|
+
});
|
|
75
|
+
}
|
|
68
76
|
export function refreshTroubleshooterInput(job, original, recovery) {
|
|
69
77
|
recovery.input = failureInput(job, original, recovery.id);
|
|
70
78
|
recovery.recoveryForAttempt = original.attempt;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agents-relay",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.165",
|
|
4
4
|
"description": "Durable async agent jobs coordinated through GitHub pull requests",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"test:fast": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=2 test/workflow-lifecycle.test.js test/workflow-v2-lifecycle-parity.test.js test/workflow-effects.test.js test/relay-store.test.js test/local-authority.test.js test/local-jobid-routing.test.js test/local-sqlite-jobid.test.js test/no-github.test.js test/scheduled-jobs.test.js test/reconcile-coordinator.test.js test/service-publication.test.js test/github-webhook.test.js",
|
|
21
21
|
"test:full": "npm run build && node --import ./test/test-store-env.mjs --test --test-concurrency=1 test/*.test.js && npm run test:verification:l3",
|
|
22
22
|
"test:verification:l1": "npm run build && node --test test/verification/harness.test.mjs test/verification/contract.test.mjs test/verification/graph-inventory.test.mjs test/verification/dashboard-drilldown.test.mjs test/verification/evidence-matrix.test.mjs test/verification/task-resolution-classification.test.mjs test/verification/planner-contract.test.mjs test/verification/force-restate-validation.test.mjs test/planner-child-recovery.test.js",
|
|
23
|
-
"test:verification:l3": "npm run build && node --test --test-concurrency=1 test/verification/harness.test.mjs test/verification/l3-sqlite.test.mjs test/verification/l3-ingress.test.mjs test/verification/l3-replay.test.mjs test/verification/objective-authority.test.mjs test/verification/l3-convergence.test.mjs test/verification/l3-review-convergence.test.mjs test/verification/l3-human-escalation.test.mjs test/verification/l3-escalation-notifier.test.mjs test/verification/l3-unknown-effect.test.mjs test/verification/l3-cli-actions.test.mjs test/verification/l3-planner-round.test.mjs test/verification/l3-planner-cli.test.mjs test/verification/l3-task-resolution.test.mjs test/verification/l3-plan-apply-convergence.test.mjs test/verification/full-recovery-gates.test.mjs test/verification/production-wiring.test.mjs test/verification/l3-full-path.test.mjs test/verification/l3-projection-recovery.test.mjs",
|
|
23
|
+
"test:verification:l3": "npm run build && node --test --test-concurrency=1 test/verification/harness.test.mjs test/verification/l3-sqlite.test.mjs test/verification/l3-ingress.test.mjs test/verification/l3-replay.test.mjs test/verification/objective-authority.test.mjs test/verification/l3-convergence.test.mjs test/verification/l3-review-convergence.test.mjs test/verification/l3-human-escalation.test.mjs test/verification/l3-escalation-notifier.test.mjs test/verification/l3-unknown-effect.test.mjs test/verification/l3-cli-actions.test.mjs test/verification/l3-planner-round.test.mjs test/verification/l3-planner-cli.test.mjs test/verification/l3-task-resolution.test.mjs test/verification/l3-plan-apply-convergence.test.mjs test/verification/full-recovery-gates.test.mjs test/verification/production-wiring.test.mjs test/verification/l3-full-path.test.mjs test/verification/l3-projection-recovery.test.mjs test/verification/l3-daemon-startup-recovery.test.mjs",
|
|
24
24
|
"test:verification": "npm run test:verification:l1 && npm run test:verification:l2 && npm run test:verification:l3 && npm run test:verification:cross-a && node --test test/workflow-production-outbox.test.js && npm run test:verification:matrix",
|
|
25
25
|
"e2e": "node e2e/runner/run.mjs",
|
|
26
26
|
"test:verification:cross-a": "npm run build && RELAY_A_DIST=$PWD/dist node --test test/verification/action-ledger-cross-branch.test.mjs test/verification/outbox-cross-a.test.mjs",
|
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agents-relay
|
|
3
3
|
description: Use when work needs durable, multi-agent orchestration across turns or processes: plan and execute Jobs/Tasks, coordinate dependencies and reviews, recover failures, and track authoritative lifecycle events. Also use for content-generation workflows (research, story, production, human review, publication) that need explicit agent roles and handoff boundaries. Not for simple one-shot tasks with no durable coordination.
|
|
4
|
+
requires:
|
|
5
|
+
skills:
|
|
6
|
+
- daemon-service-manage
|
|
4
7
|
---
|
|
5
8
|
|
|
6
9
|
# Agents Relay
|
|
@@ -124,3 +127,7 @@ Keep secrets, credentials, private prompts, and large private payloads out of du
|
|
|
124
127
|
When an ACTIVE Job contains a current-run `FAILED` task, reconciliation may create one P0-priority `troubleshooter` recovery task for that exact failed attempt. `BLOCKED` is resolved only through authoritative unblock, which records evidence and transitions the Task to `UNBLOCKED`. The recovery task is routed to a local Codex-capable worker with repository/tool access and runs under the bundled Troubleshooter agent contract. Autonomous `UNBLOCKED` Tasks are historical and feed the next Planner round instead of being retried or sent through Troubleshooter. Each Planner round is durably represented by an `agents-relay:plan:v1` PR comment created before generated Task comments and updated in place with its assessment and generated Task IDs. Troubleshooter diagnosis is event-first: it must inspect the failed task's task-scoped lifecycle event history (including ordering, duplicate/missing/late events, source and phase data) before classifying root cause or retrying.
|
|
125
128
|
|
|
126
129
|
The Troubleshooter diagnoses before retrying. A task-local or transient blocker is repaired narrowly; a shared worker/runtime/tool defect is repaired through the owning repository's P0/self-job workflow. Only after the blocker is removed does it retry the original task and verify durable progress. Historical failures from older scheduled autonomous runs do not spawn recovery work, and deterministic reconciliation does not duplicate a Troubleshooter task for the same failed attempt.
|
|
130
|
+
|
|
131
|
+
## Persistent service bootstrap
|
|
132
|
+
|
|
133
|
+
This Skill owns `agents-relay/daemon`, `agents-relay/dashboard-tunnel`. The `daemon-service-manage` Skill is required. Run `node skills/agents-relay/bin/services.mjs setup` for idempotent registration through `serviceman`; run `status` to inspect or `doctor` to fail if a service is not online. Existing process configuration is preserved. Do not create unqualified legacy PM2 names.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {spawnSync} from 'node:child_process';
|
|
3
|
+
const skill="agents-relay";
|
|
4
|
+
const definitions=[["daemon", "npx", ["-y", "--package", "agents-relay@latest", "agents-relayd", "--workspace", "__HOME__/Workspace", "--port", "8787", "--concurrency", "4", "--interval", "300000", "--refresh-ms", "300000", "--github-webhook-secret-file", "__HOME__/.agents-relay/github-webhook-secret"]], ["dashboard-tunnel", "cloudflared", ["tunnel", "--protocol", "http2", "--no-autoupdate", "run", "--url", "http://127.0.0.1:8787", "agents-relay-dashboard"]]];
|
|
5
|
+
const home=process.env.HOME||'';
|
|
6
|
+
const expand=s=>s.replaceAll('__HOME__',home);
|
|
7
|
+
const action=process.argv[2]||'status';
|
|
8
|
+
if(!['setup','status','doctor'].includes(action)){console.error('usage: node skills/'+skill+'/bin/services.mjs setup|status|doctor');process.exit(2)}
|
|
9
|
+
const run=(cmd,args)=>{const p=spawnSync(cmd,args,{encoding:'utf8'});if(p.error)throw p.error;if(p.status!==0)throw new Error(cmd+' failed: '+String(p.stderr||p.stdout).slice(0,300));return p.stdout};
|
|
10
|
+
try {
|
|
11
|
+
const pm2=JSON.parse(run('npx',['--yes','pm2','jlist']));
|
|
12
|
+
const report=[];
|
|
13
|
+
for(const [service,binary,args] of definitions){
|
|
14
|
+
const id=skill+'/'+service;
|
|
15
|
+
const current=pm2.find(x=>x.name===id);
|
|
16
|
+
if(action==='setup'&&!current){
|
|
17
|
+
if(skill==='family-tutor')throw new Error('family-tutor/orchestrator missing: provision product runtime before setup; serviceman must not infer its working directory');
|
|
18
|
+
const result=spawnSync('serviceman',['define','--skill',skill,'--service',service,'--apply','--',binary,...args.map(expand)],{stdio:'inherit'});
|
|
19
|
+
if(result.error)throw result.error;
|
|
20
|
+
if(result.status!==0)throw new Error('failed to define '+id);
|
|
21
|
+
}
|
|
22
|
+
const live=action==='setup'?JSON.parse(run('npx',['--yes','pm2','jlist'])).find(x=>x.name===id):current;
|
|
23
|
+
report.push({service:id,state:live?.pm2_env?.status||'missing',registered:Boolean(live)});
|
|
24
|
+
}
|
|
25
|
+
console.log(JSON.stringify({skill,services:report},null,2));
|
|
26
|
+
if(report.some(x=>x.state!=='online'))process.exitCode=2;
|
|
27
|
+
} catch(e){console.error(e.message);process.exitCode=2}
|